From 7d05e777adef4dbe72603bf6745744cf5536f566 Mon Sep 17 00:00:00 2001 From: Gregory Jefferis Date: Thu, 17 Sep 2026 17:00:23 +0100 Subject: [PATCH 1/7] Add c_pointsinside winding-number point-in-mesh test Robust generalised (solid-angle) winding number implemented in C++ and parallelised over query points with RcppThread. Thresholding abs(w) > 0.5 classifies points as inside a closed triangle mesh. Unlike a closest-point signed-distance test (e.g. Rvcg::vcgClostKD) it does not depend on surface normals and has no ray-casting tie-breaking, so it does not produce the spurious "outside point classified as inside" results that normal-based tests give near thin protrusions or sharp features. Intended as the accelerated back end for nat::pointsinside(). The exported c_pointsinside() returns a logical vector; the underlying c_mesh_winding_number() (raw winding numbers) is kept internal, useful for correctness checks. Default threads = 4 matches the rest of the package. --- NAMESPACE | 1 + NEWS.md | 5 +++ R/RcppExports.R | 4 +++ R/inside_mesh.R | 45 +++++++++++++++++++++++++ man/c_pointsinside.Rd | 53 +++++++++++++++++++++++++++++ src/RcppExports.cpp | 15 +++++++++ src/inside_mesh.cpp | 77 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 200 insertions(+) create mode 100644 R/inside_mesh.R create mode 100644 man/c_pointsinside.Rd create mode 100644 src/inside_mesh.cpp diff --git a/NAMESPACE b/NAMESPACE index b0708b5..a7ae353 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,7 @@ export(c_ListofMatrixRows) export(c_coords21dindex) export(c_ijkpos) export(c_listlengths) +export(c_pointsinside) export(c_seglengths) export(c_sub2ind) export(c_topntail) diff --git a/NEWS.md b/NEWS.md index d9cf5f5..0567165 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,11 @@ thread policy (respecting `getOption("Ncpus")` and `OMP_THREAD_LIMIT`, else a conservative 2) rather than a hard-coded 4. Pass `threads = 0` for all cores, or an integer to override. No new package dependency. +* add `c_pointsinside()`, a robust point-in-mesh test based on the generalised + (solid-angle) winding number. Unlike a closest-point signed-distance test it + does not depend on surface normals, so it avoids the spurious "outside point + classified as inside" results that normal-based tests can give near thin + protrusions or sharp features. Parallelised over points with RcppThread. # natcpp 0.3.1 diff --git a/R/RcppExports.R b/R/RcppExports.R index c800624..161eb6e 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -57,6 +57,10 @@ c_coords21dindex <- function(xyz, origin, voxdims, dims, clamp = FALSE) { .Call(`_natcpp_c_coords21dindex`, xyz, origin, voxdims, dims, clamp) } +c_mesh_winding_number <- function(points, vertices, faces, threads = 4L) { + .Call(`_natcpp_c_mesh_winding_number`, points, vertices, faces, threads) +} + #' Convert a matrix into list of row vectors #' #' @details Typically this will be for 3D coordinates but there are no limits diff --git a/R/inside_mesh.R b/R/inside_mesh.R new file mode 100644 index 0000000..a967666 --- /dev/null +++ b/R/inside_mesh.R @@ -0,0 +1,45 @@ +#' Test which points lie inside a triangle mesh (generalised winding number) +#' +#' @description Robust point-in-mesh test based on the generalised (solid-angle) +#' winding number. For a closed mesh the winding number is approximately +#' \eqn{\pm 1} for interior points and \eqn{0} for exterior points, so +#' \code{abs(w) > 0.5} classifies points as inside. Unlike a closest-point +#' signed-distance test it does not depend on surface normals and has no +#' ray-casting tie-breaking, so it does not produce the spurious "outside +#' point classified as inside" results that normal-based tests can give near +#' thin protrusions or sharp features. +#' +#' @details The mesh should be closed (watertight) and triangular; the result is +#' independent of face orientation (winding). This is a self-contained \eqn{O(P +#' \times F)} implementation (P points, F faces), parallelised over points with +#' \pkg{RcppThread}; it is intended as the accelerated back end for +#' \code{nat::pointsinside()}. For very large meshes combined with very large +#' point sets a spatially accelerated method (BVH / fast winding number) would +#' be faster. +#' +#' @param points An Nx3 matrix of query point coordinates (or anything +#' coercible with \code{as.matrix}). +#' @param vertices An Nx3 matrix of mesh vertex coordinates. +#' @param faces An Nx3 integer matrix of 1-based vertex indices (one triangle +#' per row), e.g. \code{t(mesh$it)} for an \pkg{rgl} \code{mesh3d}. +#' @param threads Number of threads to use (default \code{4}, matching the rest +#' of \pkg{natcpp}). Set to \code{0} to use all available cores. Keep it at or +#' below 2 in package examples and tests to respect CRAN's core limit. +#' @return For \code{c_pointsinside}, a logical vector of length \code{nrow(points)} +#' (\code{TRUE} = inside). For \code{c_mesh_winding_number}, the numeric +#' winding number for each point. +#' @export +#' @rdname c_pointsinside +#' @examples +#' \dontrun{ +#' # tetrahedron +#' V <- rbind(c(0,0,0), c(1,0,0), c(0,1,0), c(0,0,1)) +#' F <- rbind(c(1,3,2), c(1,2,4), c(1,4,3), c(2,3,4)) +#' c_pointsinside(rbind(c(.2,.2,.2), c(2,2,2)), V, F) # TRUE FALSE +#' } +c_pointsinside <- function(points, vertices, faces, threads = 4L) { + w <- c_mesh_winding_number(as.matrix(points), as.matrix(vertices), + matrix(as.integer(faces), ncol = 3L), + threads = threads) + abs(w) > 0.5 +} diff --git a/man/c_pointsinside.Rd b/man/c_pointsinside.Rd new file mode 100644 index 0000000..ff4f290 --- /dev/null +++ b/man/c_pointsinside.Rd @@ -0,0 +1,53 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/inside_mesh.R +\name{c_pointsinside} +\alias{c_pointsinside} +\title{Test which points lie inside a triangle mesh (generalised winding number)} +\usage{ +c_pointsinside(points, vertices, faces, threads = 4L) +} +\arguments{ +\item{points}{An Nx3 matrix of query point coordinates (or anything +coercible with \code{as.matrix}).} + +\item{vertices}{An Nx3 matrix of mesh vertex coordinates.} + +\item{faces}{An Nx3 integer matrix of 1-based vertex indices (one triangle +per row), e.g. \code{t(mesh$it)} for an \pkg{rgl} \code{mesh3d}.} + +\item{threads}{Number of threads to use (default \code{4}, matching the rest +of \pkg{natcpp}). Set to \code{0} to use all available cores. Keep it at or +below 2 in package examples and tests to respect CRAN's core limit.} +} +\value{ +For \code{c_pointsinside}, a logical vector of length \code{nrow(points)} + (\code{TRUE} = inside). For \code{c_mesh_winding_number}, the numeric + winding number for each point. +} +\description{ +Robust point-in-mesh test based on the generalised (solid-angle) + winding number. For a closed mesh the winding number is approximately + \eqn{\pm 1} for interior points and \eqn{0} for exterior points, so + \code{abs(w) > 0.5} classifies points as inside. Unlike a closest-point + signed-distance test it does not depend on surface normals and has no + ray-casting tie-breaking, so it does not produce the spurious "outside + point classified as inside" results that normal-based tests can give near + thin protrusions or sharp features. +} +\details{ +The mesh should be closed (watertight) and triangular; the result is + independent of face orientation (winding). This is a self-contained \eqn{O(P + \times F)} implementation (P points, F faces), parallelised over points with + \pkg{RcppThread}; it is intended as the accelerated back end for + \code{nat::pointsinside()}. For very large meshes combined with very large + point sets a spatially accelerated method (BVH / fast winding number) would + be faster. +} +\examples{ +\dontrun{ +# tetrahedron +V <- rbind(c(0,0,0), c(1,0,0), c(0,1,0), c(0,0,1)) +F <- rbind(c(1,3,2), c(1,2,4), c(1,4,3), c(2,3,4)) +c_pointsinside(rbind(c(.2,.2,.2), c(2,2,2)), V, F) # TRUE FALSE +} +} diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index e6ff2f2..6a981d0 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -81,6 +81,20 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// c_mesh_winding_number +NumericVector c_mesh_winding_number(NumericMatrix points, NumericMatrix vertices, IntegerMatrix faces, int threads); +RcppExport SEXP _natcpp_c_mesh_winding_number(SEXP pointsSEXP, SEXP verticesSEXP, SEXP facesSEXP, SEXP threadsSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< NumericMatrix >::type points(pointsSEXP); + Rcpp::traits::input_parameter< NumericMatrix >::type vertices(verticesSEXP); + Rcpp::traits::input_parameter< IntegerMatrix >::type faces(facesSEXP); + Rcpp::traits::input_parameter< int >::type threads(threadsSEXP); + rcpp_result_gen = Rcpp::wrap(c_mesh_winding_number(points, vertices, faces, threads)); + return rcpp_result_gen; +END_RCPP +} // c_ListofMatrixRows List c_ListofMatrixRows(const SEXP& object); RcppExport SEXP _natcpp_c_ListofMatrixRows(SEXP objectSEXP) { @@ -173,6 +187,7 @@ static const R_CallMethodDef CallEntries[] = { {"_natcpp_c_ijkpos", (DL_FUNC) &_natcpp_c_ijkpos, 5}, {"_natcpp_c_sub2ind", (DL_FUNC) &_natcpp_c_sub2ind, 2}, {"_natcpp_c_coords21dindex", (DL_FUNC) &_natcpp_c_coords21dindex, 5}, + {"_natcpp_c_mesh_winding_number", (DL_FUNC) &_natcpp_c_mesh_winding_number, 4}, {"_natcpp_c_ListofMatrixRows", (DL_FUNC) &_natcpp_c_ListofMatrixRows, 1}, {"_natcpp_c_listlengths", (DL_FUNC) &_natcpp_c_listlengths, 1}, {"_natcpp_c_topntail", (DL_FUNC) &_natcpp_c_topntail, 1}, diff --git a/src/inside_mesh.cpp b/src/inside_mesh.cpp new file mode 100644 index 0000000..22cc0e1 --- /dev/null +++ b/src/inside_mesh.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include + +using namespace Rcpp; + +// Generalised (solid-angle) winding number of a triangle mesh at a set of query +// points. For a closed, consistently oriented mesh the winding number is ~ +/-1 +// for interior points and ~0 for exterior points, regardless of orientation, so +// thresholding |w| > 0.5 gives a robust inside/outside test that -- unlike a +// closest-point signed distance -- has no dependence on local surface normals +// and no ray-casting tie-breaking. Signed solid angle per triangle uses the +// Van Oosterom & Strackee (1983) formula. + +// [[Rcpp::export]] +NumericVector c_mesh_winding_number(NumericMatrix points, + NumericMatrix vertices, + IntegerMatrix faces, + int threads = 4) { + const int np = points.nrow(); + const int nv = vertices.nrow(); + const int nf = faces.nrow(); + if (points.ncol() != 3) stop("points must be an Nx3 matrix"); + if (vertices.ncol() != 3) stop("vertices must be an Nx3 matrix"); + if (faces.ncol() != 3) stop("faces must be an Nx3 matrix"); + + // Copy into contiguous std::vectors for thread-safe, cache-friendly reads. + std::vector vx(nv), vy(nv), vz(nv); + for (int i = 0; i < nv; ++i) { + vx[i] = vertices(i, 0); vy[i] = vertices(i, 1); vz[i] = vertices(i, 2); + } + std::vector fa(nf), fb(nf), fc(nf); + for (int i = 0; i < nf; ++i) { + const int a = faces(i, 0) - 1, b = faces(i, 1) - 1, c = faces(i, 2) - 1; // 1-based -> 0-based + if (a < 0 || b < 0 || c < 0 || a >= nv || b >= nv || c >= nv) + stop("faces contains a vertex index outside [1, nrow(vertices)]"); + fa[i] = a; fb[i] = b; fc[i] = c; + } + std::vector px(np), py(np), pz(np); + for (int i = 0; i < np; ++i) { + px[i] = points(i, 0); py[i] = points(i, 1); pz[i] = points(i, 2); + } + + NumericVector out(np); + double* out_ptr = out.begin(); // NumericVector is not thread-safe; write via raw pointer + const double inv4pi = 1.0 / (4.0 * M_PI); + + const size_t nThreads = (threads > 0) ? static_cast(threads) + : std::thread::hardware_concurrency(); + + RcppThread::parallelFor(0, np, [&](int i) { + const double qx = px[i], qy = py[i], qz = pz[i]; + double omega = 0.0; + for (int f = 0; f < nf; ++f) { + // triangle vertices relative to the query point + const double ax = vx[fa[f]] - qx, ay = vy[fa[f]] - qy, az = vz[fa[f]] - qz; + const double bx = vx[fb[f]] - qx, by = vy[fb[f]] - qy, bz = vz[fb[f]] - qz; + const double cx = vx[fc[f]] - qx, cy = vy[fc[f]] - qy, cz = vz[fc[f]] - qz; + const double la = std::sqrt(ax*ax + ay*ay + az*az); + const double lb = std::sqrt(bx*bx + by*by + bz*bz); + const double lc = std::sqrt(cx*cx + cy*cy + cz*cz); + // numerator: scalar triple product a . (b x c) + const double num = ax*(by*cz - bz*cy) - ay*(bx*cz - bz*cx) + az*(bx*cy - by*cx); + // denominator + const double den = la*lb*lc + + (ax*bx + ay*by + az*bz) * lc + + (bx*cx + by*cy + bz*cz) * la + + (cx*ax + cy*ay + cz*az) * lb; + omega += 2.0 * std::atan2(num, den); // signed solid angle of this triangle + } + out_ptr[i] = omega * inv4pi; + }, nThreads); + + return out; +} From b922d1f97bc0f806784ce36d0a80d24c24144883 Mon Sep 17 00:00:00 2001 From: Gregory Jefferis Date: Thu, 17 Sep 2026 17:00:23 +0100 Subject: [PATCH 2/7] Test c_pointsinside on analytic and real meshes Analytic tetrahedron (interior w ~ +/-1, exterior ~ 0), orientation independence, thread-count invariance and input validation, plus a real visual-CA1 mesh (bundled in testdata) where the four points a normal-based test wrongly called inside are confirmed outside and 2000 bbox-sampled points match an independent oracle (CGAL Side_of_triangle_mesh) baked into the rds. All parallel calls capped at threads = 2 to respect CRAN's check-farm limit; suite passes with _R_CHECK_LIMIT_CORES_=TRUE. --- tests/testthat/test-inside-mesh.R | 75 +++++++++++++++++++++++++++ tests/testthat/testdata/ca1_mesh.rds | Bin 0 -> 48394 bytes 2 files changed, 75 insertions(+) create mode 100644 tests/testthat/test-inside-mesh.R create mode 100644 tests/testthat/testdata/ca1_mesh.rds diff --git a/tests/testthat/test-inside-mesh.R b/tests/testthat/test-inside-mesh.R new file mode 100644 index 0000000..1fd7740 --- /dev/null +++ b/tests/testthat/test-inside-mesh.R @@ -0,0 +1,75 @@ +# Analytic tetrahedron: interior winding number ~ +/-1, exterior ~ 0. +tetra <- function() { + V <- rbind(c(0, 0, 0), c(1, 0, 0), c(0, 1, 0), c(0, 0, 1)) + F <- rbind(c(1, 3, 2), c(1, 2, 4), c(1, 4, 3), c(2, 3, 4)) + list(V = V, F = F) +} + +test_that("winding number classifies an analytic tetrahedron", { + m <- tetra() + inside <- rbind(c(0.2, 0.2, 0.2), c(0.1, 0.1, 0.1)) + outside <- rbind(c(2, 2, 2), c(-1, 0, 0), c(0.6, 0.6, 0.6)) + + wi <- c_mesh_winding_number(inside, m$V, m$F, threads = 2L) + wo <- c_mesh_winding_number(outside, m$V, m$F, threads = 2L) + expect_equal(abs(wi), c(1, 1), tolerance = 1e-6) + expect_equal(wo, c(0, 0, 0), tolerance = 1e-6) + + expect_equal(c_pointsinside(inside, m$V, m$F, threads = 2L), c(TRUE, TRUE)) + expect_equal(c_pointsinside(outside, m$V, m$F, threads = 2L), + c(FALSE, FALSE, FALSE)) +}) + +test_that("classification is independent of face orientation", { + m <- tetra() + Frev <- m$F[, c(1, 3, 2)] # uniform winding flip + p <- rbind(c(0.2, 0.2, 0.2), c(2, 2, 2)) + w <- c_mesh_winding_number(p, m$V, m$F, threads = 2L) + wr <- c_mesh_winding_number(p, m$V, Frev, threads = 2L) + expect_equal(wr, -w, tolerance = 1e-9) # sign flips, magnitude does not + expect_equal(c_pointsinside(p, m$V, Frev, threads = 2L), + c_pointsinside(p, m$V, m$F, threads = 2L)) +}) + +test_that("threads argument does not change the result", { + m <- tetra() + set.seed(1) + p <- matrix(runif(300, -0.5, 1.5), ncol = 3) + # keep <= 2 cores to respect CRAN's check-farm limit + w1 <- c_mesh_winding_number(p, m$V, m$F, threads = 1L) + w2 <- c_mesh_winding_number(p, m$V, m$F, threads = 2L) + expect_identical(w1, w2) +}) + +test_that("input validation errors on malformed matrices", { + m <- tetra() + expect_error(c_mesh_winding_number(matrix(0, 2, 2), m$V, m$F), "Nx3") + expect_error(c_mesh_winding_number(rbind(c(0, 0, 0)), matrix(0, 4, 2), m$F), + "Nx3") + bad <- rbind(c(1, 3, 2), c(1, 2, 99)) # vertex index out of range + expect_error(c_mesh_winding_number(rbind(c(0, 0, 0)), m$V, bad), + "vertex index") +}) + +test_that("real CA1 mesh: false positives outside, matches reference", { + f <- test_path("testdata", "ca1_mesh.rds") + skip_if_not(file.exists(f)) + d <- readRDS(f) + + # the four points a normal-based test wrongly called inside are all outside + expect_false(any(c_pointsinside(d$false_positives, d$vertices, d$faces, + threads = 2L))) + expect_equal(c_mesh_winding_number(d$false_positives, d$vertices, d$faces, + threads = 2L), + rep(0, nrow(d$false_positives)), tolerance = 1e-3) + + # bbox-sampled points match an independent oracle (CGAL Side_of_triangle_mesh) + set.seed(d$seed) + bb <- apply(d$vertices, 2, range) + P <- cbind(runif(d$n, bb[1, 1], bb[2, 1]), + runif(d$n, bb[1, 2], bb[2, 2]), + runif(d$n, bb[1, 3], bb[2, 3])) + stopifnot(identical(dim(P), dim(d$points))) # reproducible sample + expect_equal(c_pointsinside(P, d$vertices, d$faces, threads = 2L), + d$inside_ref) +}) diff --git a/tests/testthat/testdata/ca1_mesh.rds b/tests/testthat/testdata/ca1_mesh.rds new file mode 100644 index 0000000000000000000000000000000000000000..44889c8ed0a1e65afad0719b7cf68aaa1aa8a486 GIT binary patch literal 48394 zcmW(+Ra6uV6WyS@TR>4#x_d1^X+=^(K)O3O#GpfvE&(Z}q;pphP->CxrMqj1jsH9U zJk7(La~~$oxp!vx;wS+BH}LmPO1aPOmkM1UgOe`OL>_&5c)Q3Y`87s_HAU}@p7Gs> z8#Knojyl3wsYMSSGm22XseD{tVf2)#NUoNcG!_Ki%=I?kwc3SEwxR1W!N@~d8*HGh znRcIo3f)J6B`FzRZGHn7%R^#OVfy>-Wl3P@lT2_MaGbuSb~`kj^`#?r^oGE?%KqX*lhT$6$?zS z34>5DUKYLk{)p}Fl8Y~~4Q+gRH3jvfGa=q|eAe5H!Rg@j<<#H@%^RJ-bu1b)h1|sM8V=N5ovNvxVNSCw zu&};7W!pPH*|2C|Vq9|;R#^TGOuerw;btmCwxy6Rd+^2?2}_cV!VXw1;&yrH@!V+H z5FEFm_%qmK)MicD^is{S^O62+9eUaJbp3D&GbMX-Q_xXXj&IH1c6K7jPPu5z;IPDn z#2bPI8c&?pJUiTE^ddHhU1ClN8ps{wfi2OWZ{0!xwg9VECq`(FX=Vp2z^q}7mou=T zRMnSJefU)_A4y`iV(^dUnw$B*`YnpM^AGs?f-HA?){80S*98Mad05&eY$JE@uQ$GUO6h<9`!wBP$NWA+eEUe6pL0QE|w= zPNMfK+=3=Cm(UxBL0UnfIC?r8!VrvTxjBf>I=%MJe;%1cI9Fd8gXLi{5z=GBM?VLz zPBt}V3y|mnB9F#48v9O(prUw!7FZ3r&_uI#viE8RlM|rtj?q`>(+G{s?ZZQNsO~h+ zf8Ao#Mcl4Izq>*EI!&r}54bG8CU)d68#idex;Cd=e=RJA68C~j^Rk3~&YjtwX3l7K z^c0-q61s`6(0hG@mpFg$ufmZI7?g-@J>EQ3Ts&FvCAMD;cPPptk;iIBWr;(3r-P@m zfeu<9hZ|%&@WHC7L}Dkw@J2DwX-D%Avx*Dug6-zT!mz~PTaDcLSahI3-V1!qBqg@S z{rBN(Vp~Bs>`)Vp=fiBGiO8E1Md&QnkJx63apNEQeGBY>h-tg%!>I9Hp#ASzckCUZPw$F* zUAjlghDJ?c@$QR6T^n=*k&}3MHMG{JN1(*TA$^tN;e_{nua&smVhqCPjk|yATS@Uq zcwMu_iUh~8O+J`+SU+U%MQ^GZ^zp!P*Jix7v3l|N5PzLQ-!jOr5ljE zSE6ENIlaWxKiJ&eo?&HG@s z8N|>w^uBPzB{tv+ZIINNi^)FOT>V4rV0tf2JUvs&x!UiA4SH)J^A2FNRKbKDKP2H>8-QHz4GjR{ok--EU-sBPEyI-l8sQ}EV@Z&3d>@gOqKvYXJQabHZ zdiO97V{j<{5#}}K@Z)k-uVDLZhHo9q9djp^$oYt|pQwhHNZ|`1guH{z=M2H1WMUjd zWjbOQnb1lLQP!XogBQ}ed%0`fZ~Kvq7(s~n9N0osH7#`zJ0QlVCg9BblD0L7F{v+c z&W%@fJpaw4XcgpVVqb^Ta4;<~>{{>q(%pX(Mi8?3O_W|d3msn4ipCs1gz8~WZ3pi! z9nQg|i{IYx%|{>K+`|{coT1^-e6kKWX0dqK8PRr7Z`FlMk=Qe6%hF9$Q@@aNa#|&j z%Z@$RAkW9p$MLbSylv6U%*?O98{68VIi0m=*b4i7#)r7C^<|L$CBa7I80{fXbqo6a zNnJkQ2i)o0FM5Ay=9k*Xb+qqUk&PRgnqvk9n{o7o8= zA7zgOOZ!!otmZ2pVmEEL4{SJCW&W~`9!p-pE8KCp&jbxQb!bDo?uis0t$)-c;qX2jsL$>!cGnb?1poj${H>zQia- zf5Oceti%4}O;r2#Bm#q#vvHD=Nl4<%RXTPcKll%BNO5dlA;C3}aAkSCsgOrqjq@$F z3MRDNT<+$0IoGbl*lQBQ+Fq_Defh09#yO~0fPoz|p)_@0&hvfO>yryAJ@w_%{DOy0Br9&V$FJ=6&>!H-v~Jj(??<;tXdh+;OSbDV>NOZQ@l1UcSP<$GjU%@E~bBwru0 z^r(jP(m~I`iA!}XBy*#~)h4@l?!TB&cV3vrNej%u`MT9LLVQ_MH6hE@CVzO-HGg8Q zVEt^-d#PY3plW7Nv)%Q(oIcR+hV>-*1bR_qUhjY}!GElvO#0ypjNmMO4Xb)YZ09QJ zhiT=*w4SvPn=R$KrHQcdFPw>2$O--o;%~iM*O-YSPOmG?iQ?_L4qxJC+ZoYJKfBjK zNwXBajXZbZDjv&>LE#JBNB>L5)iSYBum@lFB82zAoBn-4F9qfiKG2&>Hx_rb)M_!N zEsaN?!4|XkDff27$gR(Z;E%-r?OaV$teFL4B$-&rO;hW)w12t6k z8hK#7As)rzPZ6kU+ia>?FEScR-(oPAjXF!0pB&E<~^xn+rUD3-(ug%S|;s;Ng4!sJR!MZFI!>JgUA%m^-~Fe%dI2D*Km`feIc0itZYDIw8pl}v!2oY-=mTdg zRj(U-$UsqJrS^_%3bY&GF50EQO~LmVZ^O|D+HP9hl-iF(4Rb!TaFC+|K?lmMwE2L6 zb*I%IRlFd1=6g5K5!8?UUw&k(BU$2*o3s(jrX5dmHn^p*0@QBpTqfLXqMFc!@_d(+ zpj%!O=d@UOvF70c-3=K-wD-IoasrSR-DBOJ$V=W&i|j?qoI}pY_erczu=bexvL_Z5%g|2aAXE-?!Tc1E&UWT)BM9`9yi(@9WkKvE^gOe(3 zNYk1%D5sw_-40 zd3*oP@~z+pc8B2^>(F*YU>9Z&lmpg7t7N>qpeb}+%gP|7Dcbbz+vlUs`8iZsPtSWm3OJJAuX5mibI$V-i;I5$9crp?5PF{-fKHuwJw$@#-_&i>v+uE*2s7<OG&-iDecmt1D<<~ z)0Xk-7TU}Q$LA}uyEy-7{Ox4{Ja7E;z;KTn}3vqfn9Okzsc$dQhAvuA=`AnkmC)a;FDsO}ky@ZJUU0oiZ z7I(=*ayj^y03UAYe`CkqyR zZv3qiSzLKPK=vc*6LQbwlBcMdY9vZQUkxO&J_92B(!H!xH@cuFMGGn)ZRj3<81J{$ z?MAFJ2MtjkbLdW}L$r+nQa^^6?nT_L-DFy^S(qj{qxRky@`lombJ^@1WSj6WiZ}E= zu?2^{be&q9`V8?nkQwa?0_h|S`QKC@Jn6_oHi|k zL~|ExnX$0`opu!UI<@MKt2^A!qsiq@MKI*V@GSIp3;mz0Z#txOo)E+LY)iU6PiQv<|g5|3hp91mV|*q5L1_yq>-4px0T< zSh&rd7nQIbOObQ8aF4#K>wZ3I$>$)D=KTe@>CT0J@EcMFWt9({+D*WFp3ovEeK7{J z=Yof>U%^)9_#JRArxbg_;JGejcS81jiKGb-xKJ%ROVEZ;qUG+7wbD<2|5p{2uV=>_ zshE(fn{1>x!CXX1-sIBw6A5IG$NhE@5M2-hAH3Ir3Y{*6JGuAno0(wJ(#_ z$NM!Ti_`_#z`Jh0*$Brwlz3ugemhK_jDMa$Gj{1^u)apa?Yml+{dMGj-NPm?cQeDY zJS}cT-G(+uHK_iRp=xl(7VyLh0R_(wc3P`GL)=X3EZBJCu650SU9*z0y!w?B*Hum@ z^-9Qgh>JXEeolk)Ydgv}FK|TgA#HhN*VUKee#nBx$_n34vb}$0W|`J^lMmJJehZQ$ z3mcOpX0+a4N;ZMvq9HdE)_%c+EJwSA2>zRcGJuqbrPy~vdf*s7mH01(-wt^TiEM$% zGvq(5vA9g2zWbmlf$5mps8e-ds*085gC|Sq{PpdXZnKQU!z}uMoO)?%1^ei!3o#uV z?NFDWaqyK#5W=Y1;aW{QL_P0AO=ug5l=1Ro#rkg4rvU3rNmkOdF2AhjBHa95wl_QV z(*4D^@?Rg4$HVJ&mM7yfHzBOsEa4sDVzs8@kwIyLBtc)0URA221!?5e1$t?qNZiJd zpjgS2&F^URx%QP{ae+wPo=hdi^%LVS3ZcGU2#qsI{82Zb+eR*U80W=0kz)x7KUQ*i zF&0{QHFSrrGUK$KdN?= zYD<+2#5kTUo52RU!Q&n>o|=}JiA&f*TPXi{%Pq#cc!BjCYdn87DMRYEp{I_F;7sFh zn{(=-BzgtG&(yns9Q*DH&aeKGBwnmp=ekfNOPtj(`c(i1i>e)dF3Ioi#;#mf{m-eSuV9jPlkD=5>}>EsJbAkLW`M%4myrY*1YMXP&=et!nkb^ zmGGlShtJyYA||}etjiNG<2wJDh=Gnv$Bf?_CU(NrLk~MS7Qwn=ER`wp}utV`=?OJs&^^o4n>AcmRWDouUNaPvEXg+Q^3P zTS!D$Tr=}A??#a07w;4B+Q*LB)jPBZd(NBwEe)^^@XabP>jtqSh3OEvOFez*IYWK# z6XH4arSom91k6^QoHIgo#98(ACAy z8NX^2UAo%%-8k^9{X%}=-C(<$$;pv2OL)gR{C&ORi34)+jl@DQd-PAwoFF>0!Va^D z+)g}rbkX^VW&d1G1U__f8+UC*8S9{R(TfR~UEhK5v{`HbQIe3l5OmP#Suy&3A1K`dy zi)uX6r~$})yF8zxb%ms8D7zX!Zur~Zclo6<8Wqi7H@nS%F6Le>0Y*jEQtS8SJB-z< zuOjpA+mN39Rj+Ez@#ry z_VWksB>m~UT&;3suPxXO&hEhr9Hjiz_kWcC%L1|a zp01D@GxJLgC*GHRa=v5C>3T!=JJoNrS-dJmGwj}0#6}C_NOJ(mO=lS}@^hNm@lFmT z?1#ub*x_rth=t|Xv2hQ=YL1?-w|<1!)~%f z0X1SV^)sTHzQi^IRDcr&KC4slE^r|+8oXARoY4RVU3b2DKp%xj%kncabp%VPm9eFn z3Urt(w<&rJlMepw4c?}6HA(uaaYp`~^a5Y+uWN__pA2Puxh)+{-u&U)H!2nI=`e0s za3|ER&LBCloD)zHVCuqJ%(y-D<~;I5$I#L>ciI;`ky>_V0J`Z8_ zRULhaZn9Y|r9C-5m4kjjY6sp?aIEtaq$J_xL{g_fpe40GojheLnQzu6S?m`7&>OC*<31lAoT(U;4q*i{kP404Lx=F=GvC@`O7rp`BQm_ZfE5F_bKSop4_&nhHY%G zHHL7}4Jxeo{nB*X_JPXD_FH{1L%E0?=K zU4hq8X_K6Vto_0YOh$RWNv?cT-^rf^Q6#?^M;1o@SVcMmbpn$Kt0@ zcD@~5r;m#gT|mMVw%phT64!XN(otG;?NrzI;6zaBSHu~+xI0OIl&IOw&>Z|_iOa8& zeei`l+L|uv2b?o7K{*VOZkU@@bF{y z8CE%d)`3GYsa~))qWlZKnsYjCpg`nSBq&~tj*t!VhY$Bt3%7`Y8ss!dQ+vfB{bg&C zd**c0vCi3D_qK8$`d#0c+LHJ8zd!w9CQ0V>Rq^VoQMAMgBU0Xx2JS!RcFU=r1P#E( zJf)>&Aw8ScBa-fR`I0l^B{C#ubM=GX-_>cyON(QJuhDl3TAuV3LS>WNUBJf9DK;0o zmI?M ze2b7!!Bb9i#i&+kNE_l@9(l5N8tM%;InWc>9TS%!EDL69|kwTt%~+a{5D`3=*b|>tt$X?@NKS zxj(T-E2o*M6#R{3uR`2w*jis%619^twvZG)%9<~7zyt+y?88N)3aEAA=d~lK{U#La3F50 z8O4Nh-srnP1SHL%7JT$_dI9*92t@{|&y3hl)9)iz?p((LtBRhyQ$lTtery`lzQ*|FIT{j!`S<5r zZgfOXY@bBBJ=cyx?4A3ZybTr_@z90}$Fd*W6EsNTDLzy%2(WP2fCc(x>``87K9yUP*@iMb zkJqBqjrclDl)FU0TN2##dzJe@x5eL0JufI=qEKf~ffO&}EEk-pD112PW_`MI2(^}$ zSW3zWm)dyVzGp&L%QyGI1gh1dL}+Nc&(D%ct`Oyq(<6z^r76f zA@xAmD1(rHJ)TGXn`%s*om>!+A~$;DzJCYx9DP8$_Zkp-By4!GN5xRCaN;rN5RXe) zZ@haY$FCs2KWp^{(_Se4UkBY z0m3YqSAY7k#Hb>zxUGlLtzDY70&YJx5;|Q>;N%8i?5%rSu@f%^&uPs?ta9R6 z*J9%A(h~@7G}5W^D>zzL-S$(FHR6D8A!Xj5}|y3dK+bXKbY`_ZxcFarF~& zHkaN8ndR6feK^GW*Z3W^b4aZo_n(j&j-iDCxD-Lkvygjxs|zK#S#JSRt3uj#EVCqU zZuh;H^CEN0Bv7{BB7^+4z%Iz?g8Iw4{8#1N85m@Y51HM_fFFIXpWEC|P9m)$%ESP> zo$PEMUwSPi2j}qNH(~{k>AxHm_-S2!>eD-8oQ~| zXU@3eFpZhOR1;%5et1Se6AjNBjTZ~hmEcR(8V^6q1 z?c&#Pq@y_5N)NlH2FDrr@9RS*1PgbewNI$iv&%+fnvgnwkM!x2C;4M(O&p0j@SN z%~&C6{#%_kGGhB;{e}hP-iEwnrgAZxkYMe$?R+Q4IX`|gBrp4A3SGmKZ%k`!xoTQQ{|-7({-gazfE zd-^4zM#-*pS|s1)7`#yz{kP`B28xpZB)~c40pRp1?C5}G^!#dc*6w|XjOs-{n#b;S z3e>qT<)DEpPE4hVT4{11JZh5@l-_c!tCUX$s-w-V)-FJtPvBcuntR|A6RD|*on-wb z2|WI-49S01R3z1e;7v;}>PJWe3t8o;b6ZcClujQ;u4|Uu>8!aI7VbgsW1b^{S4*WH zbTe!GOa4b&cf7L2P6JZ1_o~1&dgsmK!oNGGImvWZA|<(2@eu>+$8%v*@X3qY`=Ax) zw&a*e`&mlY@#TQK(DdJ-?-ZpFn zLL}92;a}QV8Mqy0GAN?{5G^I#%vJcxH_<}sqCVvPe$|^Q>ar%VjK{VFLF#dR%bnDR z75vFkg?_uQgeV*}Yj$!YK=(-(S|^)kP>wB|c-=!F^hk$AO!& zlo7B*>=xNQ4YU#d^RAU62en)UbR%z4mcMrFtYiD49YRzvyH7k);NOryZrNMM>@4)8 ziJE!y8zyG9REPsA10KZ{%K|wLLG)VB1i?Yieeq-_edXRDi+slNYB#59tJQiom+V^( zsZ5_yOj~~H##lBNp!d_@Ml9IE3*F4({LbBKzUHN}J3yDsK+eb#uy)p-NVERB#Oflq zIb?xU4||ojhw&4J4TlDZMvzW6ebIfi>5JleVFOiTT3!gQ$or1EhdRjRpf~@B)5eWt zJUEw!3{r+owVCm{h-0(t``HmkKaX4WH(2e|HGF6HdtUIfCF0|!^nvMdoBBAM_8ZUSUs%{RejcH303&#K-=5UT9(>zHW3{AtlL$D{sC%u za^E6n5VW&LU8R2G4fYh0a7&ut7)qDf&v(F&x=7sQ! zV|ANb#K<3Ss(Zg_rw2^7HHk-lq%zVd356zykexkR8@<%uL_C@+B)$>&^`L&Cd+1jT zq&>vp$r`)KVT||V{sH5~Sm>pMKf_fvclr=-v~zjdtvBsnl4J<~v!zUH3fmRax5J@- zAu6&KE3vp84d_6oeWMON6GX}oCokv0Yjnsbaa72>xA>``1{AsH#DaWR4 zy(TXgB+O_wsA|&cH>_n8Eh7kNoc$81XFx^r$)Fq=kV+D^+U@V)i9xi;W(8pe8C^d5 z^T--UE)9y)48D{hKl$;gEAFHTHEk`Me__r%-CW&cL5(IaGP^faHbq%$6|chBuYof4 zN^A2mAt`sJyRcpO10J5GrD+#8kt;BMI zZOjL3LBNICdtJ>nK#nVm3(FoV$XPt*TC|mUnx5oPsQ-57S9Xj{QB=r!%Js%aR{~_J z>fZNHHuP&GjRMbt0tyqla&pGPz}qEGDsOa!?N~MssV9fXg7jZ$4Gru=ybFgphwOmI z^Cv@n1qY-bvIalOzm86-;e}zpWdnYfRSbyvU&H%fw6^x-P=VBo-0|U&h(-l7#H2WQ z+|yN59)Xw|1{a7Y5y zT@E1PrVjZr;a1Mh87j582D4sbZDjXj{hJ-0NHUDkWk}fm08F$0z|!UHfdn`14L3gC zHE?@snf;3XERV1o_c;LW=k8%txTab=T)r4KP9V!nwV^jx{R>gtw_76>0@V+d7P=-= z{`PRHb#a#&QRv4WF0`_&w+5d$ZNET`Yx+M75+B7R43XcN)rKFLzWir;E^$(ToOk+L z{{W(TGcN5}a$0~p+szK7j((aEZbBA-5cM|quPvh8>J>_loW4upxT}AEuLb1u_*IY* zIusP>Hg&wZFpLsDJgGYDCm-#tnYC~~{L<#1a&U2}$gpYUopCq;icMNJ^RN-BWM17p zD$+q)nL3SMt7{W%&z(PHk(`iQg`T&*Mg8MWh-VlDr_VOYWi5C?cE8EunD%LTikcU? zZJi+gr-~j6MU(+MLl&EJ43Ky?3$?pEV_#C`F12H?kTnS~HsCmtLg9}=lqVrOB zP$ST~{3ruuvz>u``>KkV3Nb%TYz~_L|YAXmzW+pX`!PR97MUlml1ACuN9(FmJK)sLY`I&66>a=@ckKpm( zZ#$ND>K*lBDNYm(pb<8Fa~bKbJ>D(`;b!SNYfvTm(@vgG!@UJ~6ud9$h9vQsexcwtpM#pY zo~+(J0rG26`>VedPd*R}_p?u@u1!ad)!d~At4ll?3$+3pZQQ3cCjJ1If7#dKTWJOx z%e(FO&>o}QKL45ZKIVwF?C8)n%Y{F1*v;4&A_Yn~?6~bS7Je5!{cJu#$)oL;_KJLE zL3r-1V~_xa5}$Be+C)MGuE_9?_XJ!#bx}1ofI0Hlx4C0q&f1b+GPVfX6Xma6mDSDg zy~Vxh&l2>5-RH4_)PV@r0ZMF+E$P|%s>F4j8~;%}&$)^*jf*0ideWC_65#At-HaOi zLtJxXQz_}bVOHa?Xi_Icog?yQBV!ZQQN@;1$Flqy>S?@Y9M0d@_~zdIkaqzm=PloR z@F=^KE+?&`HgRP|yCiLnWHp6^MUfEl!6oq(GNE7aR=?1PUg3&To&EmYZ(!i{WGAQ& z`3i=n!pHgVI;!?BP^tJ-?iXexsCboNU^QMKApXWQL zO5a^ZER20BHMKL3=e(B}P5$ZJyz$Y#hn*;e{s-$VKy1!~GM{a~Lb*|OLsxpR|DBiB z>lSoIz1mjnysGqT+N=LvKs*L0Kc0H}a~C`sb>|VcfC@tT=3T9gmz-3yP}SC99su!d z(@tKNj=`{BSjcQ1*k%KJ@kwEYGYpYvaw5v_%6^&j*1Qd5gDdQ)e#stlwdD>Gb!fwdYPb5FKo`zsv^d>$T&INvWqF zJba#M&rxgJj|mXdh`RKhYgOG>L}Z0eHFUn@$qcmy)gQ$O*E|_+gykokOqf1wP6v*Y z`PT2(rx2SuJsxo~l8r)UB$J#R>>}BBD>9`>F4+6>va^C8!1oWOeEeCZHX48H3*t!Z zTMZ5jFxP(J#WHL3tyHQX^p8B$PX@?%YvOR_WH;?#vM+3@C#jWW(8pX))%+%YLOGPe>12T(&gsr9ZY;xcxB2IY3K?L zw?;N5(sGwGn?plCOD7KmB?lGAYX_=1JX)qRVITA)Q!Qd7UgR-#S(Q?=Jo=@96%sa7 z7j;HvSktV1EQzROKWP~tb;JO@6RRN)XZf7_=OOh7qV0WxVHyy4(`tjXo zP=!dt``~Z4w)(})tZtIP2<$6Y&SRdDsryq+_i0IXvRnUk*uCIy_`&^3cK}Yf8+^*j z>A7as9`@e~5yJn_L`X`VIr`(B^J}y`D9VOuL8X~lcy8e?DR~kgW>Jh+zKu&~s81{j zuLr6uoS!$~RFk?d`?28-JBYGg=b2hN-Br{31g$7ZM8t1g?i9~EV&#UM-(QYOt6-MA z^xOG_YjygAYQWx)IQv)24U3fV_YxAo{_o#CcHyLqKAsAe^xXzk>ThF9%MKYN2j(aG zrT~Gq)C+~uOfK#=9dTaj6g;07Kc#uMp*%9hE*cfsU8E~&@^c$W6C)$tLVOvMVR3)m zDAmEAtZy!A5LOHY%;))*7{En`mvWYcB;tpqBu(>o>gnKT3l9TE@a@ zlfGbxYpf-U-=>JvUIa~101lDcD_*0Xp44kCW%lcu!%mwn`}_nqLhx|8@U8|RK21IR zCrDxT?IBE5TR<)H<&;vZ9y!VQE7PaZP5f#NPm!8UEF>3lNqcHU29o=#Hl9?KI35&vx%2}3xH6Af(s?}wu6O<~jEl(XVl_Vfk@`L&@UI(U&XSV<@Wrr53NiX% zuAyf4HAoCEcp6Q~E{tHMcQY*B>`M&zr=)_q@2yf1@`~^Hf%j*9;EFUh9uS=Nk>_jt z8>R(QNXD?)bKqJ=B9B-5J%6^Xz^B@0tEDiFuv-s4l~Lus|CqP&w;lZY*Jk1yXW0>? zy!Mj8Enw;2j=kF2W(d`Ej+^%+ISa0Ohg#M4PUo2kii;b(Gg;WCZQy99;~^|N7D);E zQcv?~!vySqpz(}H8YrCpkexOw3f^hH$j#s!?{n2^tU@I5Vez2;?4L|;Cgn7m`%~RrvPtGivI0ZO(BdME{Dsblt)x^7ntg;5%bTGFT~hmBHqmOtcQ3PPU2cx#2K$f zqjJLa$VK5Jp>vbhyPhRe92M>DhF?xwnA_50&J0%4FMf%u!(E-W7aBZ)*8*98qvM}I znm9LJnuQjr*jE&9-6!XnVX}4-rq8tRbSs|!3Q-q5lM!&_9@)`*k%U|WI#4fP?}d`X zHwgBZuX`S*u~U+KCGbHsolalMVpubGm{p3<9siU|!o&irNYg_RsUj1M!rD$5O83eE z;WhbF+nr7j9B9_W=6a~dSfJ->&rNOoDsJR{Yi-d~hp_9OAzf0}x=n+R?UR5<8z z=B_=7wb}S%3R3XHKPV~@l9DrqnIJsd{PvYQ zrAxve=9K;l0t|h`d;a(H!+-QAqTtCq)XvM~N1Ol&XwnsTbTS(SNf7Vk;Y~Wj_ip%p zz(WgdZAX6mB=VdfXL?>Q+M|v!Vg5SE5Gk9$KK@3QJh(%_^Q+OF_?#J0zsm#ng-hRDNr3$>^a>0cZMa@N1IzI-lOo=-Bs z@cS_Y6|p>}w!x6RvgmH9)pN%-qRD3ZCr#~Q$eSEQE9wMx;8EU8FHA7O48|Hl;+o{e zXnw+3_V{eiX~5SHG5+JlPa)yoU*GJ10of&$%RF5Lqs}slufw=Oul4TuCrXp*Sjjlm zxV$3I)Lr@WGbspy8FJCgx&m!6W_V1DZ9`b*vI85JfOe1YpZ+7?i!9&U0{Z*8G9P5z z4|wPg|1vVuVXDErou)IlH&+96mvcf*Fb%<}ZiczvOy^_Yp^Iy(y8ThJA zePwKE2ia$R{mm$Ws-7H2T4~LqoPby2%s}b%sY~b{ISLUfzaUVxE z2{nE!&$>Rbk?}&SL>6hAcsxh_y;5B!Ti+edd3&p*B8Y>;@l(1SUk$i_{^~qw&>Qsn z+0kG59nwN23Mli5I_cfO_D>CYN$|6SkqIYR@xu2Ja$$j`KnlLG7y&RT%EQLJID+pA z+;KYGuiXkKO%UC$wxTX^DHF<(Sb;Nhe{~6*z`sto#!W$F7}KaD%xE3^&cfM+-4WK?Q|T#hFP@H8h{&r_ z3xiGwkE(v5?hgpaTBAXX%~pN*YQf}RyxFm35f4{CR6DrNfK$3do^wRfa9}JOWA+0I z+gr9M?zZsT=w&)HGyz?#FQ%%pBN3}iqLz{7fGb;5)WGh9FeZvE$h-+SR+oEU?k&## zkL=r*KHMX`iJN)M$aeuaHF_@v^2}Q@o~$!+g=uPMq3k&{zm+3{+Fy0SA%a%2U089I zq+7rWO)sEpPH=!iDiD+ud_Pq(jOy;hleL20aQI1Mec6gxrf5}^%QJZ zlkDw$pP`;q1~i`R{PR~GNS1WTtv~6vMA>i-JPpEvBmXH*j*90XTxFHNE1J`1HX^ex z)PaT175T0hBA9fF4Ot{P+d!)EZW<~#ymq}DyQ!;`cB~zY?c^Eomp2RB1K)4`*`&BY zniT_O%M?BHLhdHV9ekhMmjmtT;x?Lg6@gZ5gw&=iL8&&@SA9=#$@$VgJ6s)m*Dsjk*!DpRoqmSRP->%Hq4@cHGeA?cX|she)$|9xeEHzflrG+16(ZyUulPd$ z&AmVS5tvkq2ot5yF*sWkl6Z2xR6pplr^>*g_Pjol$h#&9Ta@^^M*^hB;P0`U0;`Ue zCMW*-@Wptqpd5*{ zK48ZJ@w1<;)Ej?70v3D9T{MAfBQMDlG>3qnys`b_X33gi8=1JZ;rsliB@MsNV?cqT z0)J!}Cx+_D4X0qtWY>gP@*4?J0p8MeEP;z`6cl-Y`=DX>Y+fpeNrkTD$7C>7)CE8P z^7iQkT)toI?~}Cn8%Sl#?qIz?*mULe2YonO+Jy6=vK1FZK))RoBY zZq{p1xn&^ER*jBy2NF=mPyyUCAugT1qV8P5ukiY=i|a|=l0)dMZDUeM5-uo)dwb1& z&5ZkvqERK_#pRd5OL36Lax!+VS`Oe0+uZYf@&E!mY%4xJQD87KSuY8?J)c(LZJ8=Z zy;VR@tCGMA7|peQdTZlup?B|or2hd`zf}cKzq7MlvvPKjIv*%IjM#O7qbSq}sM+S%K3T>4rGoI36lmw8Ss+RJeg6)^ zB;9f3n~G!y0i$6>KgS>;0RF1!(Km@+uq?EjX(#l>T7gaFPG>OL2A2b|DNqJ-tLDDK zJ0AX6wt^QUzQBv)7q(sBogja#{TYX zBi8~#o8{e|6GqQtT~|K%>bId+j%WIA6)EE6K~!`XP2syYk3V#1kZ|N`Xl^R(pdTFE z^`0i-*u5wC2}W_dK!7&@dWU=cDo$)q91Lj=QHNo%riHd@1c$3o!1d4LubSP))_Or) zN(NM5;O$+^ObrX%+`Qc^d695p=zjXT69ajXDBh>hAh3At)mXU2Inww1V7J@Yj;G~^`L|DHZi#UNDffD48D?1t34$`SIH9I&?m#wMmT>V4i=za|;>yISI~! zQ(eixK45jp=rNERj8t=NZQfC(k9S0gk4 z+w(|%K#LhLRf|6TI-m@+_q4+;s}=ycOaosa(+QYa)~`fTEU+3Ux_(^g1V*Q{a%rAE zu$D!Qc_dB&5ii*{C$0k3?ROCsF2z7SXvg}6odHVdNv^fqFJKEb>8+;C29o}xw#f_c zf%4nJY7fZ=h-$j`GL60i`^;&^nIXNT90F zfc<{-P#+cq*a?+mJ*CP(zhc0>nY9^6R-M0`O6LG)SLAl#K2>1c@>cya5BWpqnlm)} z`M?r?DJ1*659!b?)vwf|fo)y4t;}fxuv`0G+)vF#I%Qq?ECnj^C5KN$bS?+hH-jr9 zvkZW4em1tbd>^n5{#Hl|8UqeV>Z6?E8lX3?yq_MI3e=q$YZlH60Fw90o41#21omcC zVW~FcFXya(C?iM)j>wDNhek-}&po!p+SdU{tVbTgug)T0Cu3B&xdt#^sPBzL3UCxB z!%nX70R|23za2(-rgC&4c}oRQ8@#@$P2>aVqMqT3l5h}6TAY}3Yz)}#_;pWS9S8dN z0E$zc1Ypn9Ug7OKf%5%}#HxP8gRxcTpWe_$e#<@8IKl~-LXrzB%?<-eh3e%(_zj#T zo@e%=I!w&FRHF7J8s*Uy#|`ElK&vhMe22Lm7}ckBe~7yvz2?~X?7<{3GehkCx_1Jb z>vs2i|2$yy#Gk+|M*ewiUTer&9MJB+yxN`_0nEsshtEx<0^#B7Z*j_K9lv*V3C19Q z-;`l_OdX5<;K{Fw6~m&y>h9YcpLPitN7rPoFh{!d(d4Bk zLScZtk$fn*q6Ns8lHO`+UI%jU(Ip?u&jRI{k*)28VN{nLMC)sjuK2j&oONg?@|Sk6 z-m1g^>)p=ewBp^sTxyH=Of(0^)J1*O*_VK08+%3W&UYZq4xZfJvkCFZhh2R~(EWv5 zzB_JT4dfH9>%(-K03%+!kaTY!km)vI!Bh#LgvS$)CTRm_!Oq`>5#mTMH!>K{zX6e! zYwoxW@oQeVuoXuh?T4PIp@BY-c~KhXMkYYwjWf^cEd-MEwRt`Jt$-r(=Js-xTEOXw z%=;kZ3xtilMn6s`;CFvseBbj1m)DeN@tUg(*yg>h0gI0T$x%gqw?h_p()`wrt{Nq@ z54QcUPdx$LSzeQ?PY#ewqfXM2Hv#$BNrS>o-9T0TesR^%01ysj;E3x_0&(D_;Yt0& zK!{rO*kIiNm-j_7O3lg{$hRJJORDS#(vc&!G1az!Po5=ZAW{T`*V2>|LMlKeNVeq{ zHUMSeWs4xON$%7uf>*5%6YwF@v2C|Mi}gy+P{$=xnoUg-nj-PdY>@=+n~a*!r>GQ|3k?))Mk z2FD87wygxRrsJ#oD>eh=f%Xo$R%;;dTkElZs2`}imzi2t=K*e=MPSzYEWp>cmSl)& z1Ccr3FWUjiiE2OEb<4g1%B-JP0?$-ePv= z#!LD@)w$%ie9afMo?T8GUs8bhYwx$iKH7l8}VlH4Y?{!|F^CA4?U2IGj2hzrN?3MpFz}`hmgfw;n z@ygQk7Z{%bfA;b3G#xdhV_zP#wXO$>7}pmve;{0!_DE))0%FOgOs(rzfK+!aVWpKS zP+diz6tLa^DNa-C1Zx1O=^czME^0ts+|r+v{~O5bY)sil4+Bop{z1`E0^pn}r{XQx zK*ceC+~(B-(K|p--W?Bwt*>k@S{wlUhxTig-i|dHQ+X^;5&M$87rFc5z-UTSM2 z{oQ(M<%O%kfdBO6!(#aoz+c;~?K-i7JLznEU2mT!@)v>6?BjHS;1;qY?LG}~iQ0KM11k!J> zBYjTpM*eoo;JkBXJmi00pWC%#1hC83Vm^H=09^ha*?q$UKz@at-hdSK_9@S*^WQ9 zMEif}(>XY34kY8nrws@VY>AyGXo@ncK z^VM`dw!V}}2C~#h&Eo|wfa%@MzqiE`aE}GGtzD%7mlY~BaPB>K%DbpSw&)NLBt|#N z?n64~4dJ-^pb6l1_Fghbcj8XEu6%fL(Ms;r=!8tc%awqO=_^%pmILy5R5?X<9T2i_ zI0=nZ+3dN}SgGU|0aekr$OJVhHyGCI_^I7Xm(PX@bwKP#}AXxM??E0-X4E z{r=)2zU>UO@PrLoF$q-HY&isd#_LBA{d^W>(+=fGR~= zx??aMu+QUIl4u8aYCItBhvGUQO2})A-BAa^+SKgB6IBSOxh8lJ z)KAr12c-87x4Z><0jHV#e9lcdz(2B0rG4HD_>9$$YG~-ZyyfrJ7aIX_dFnSYziRGe zq3!lt)w)0$5R0+OL(dP+`68jX07z@y`f`?b0b$uIYsbkpAgWECO!gQ`{ab8G7%^?JwC46V!6CRF3P^)}XOz)fAtFtd;X z%<1!37ZKHUwHFUQy0vk6Hfxs9njQnm$?&Pw_TyaM2~la&NIRe?Tjv>lLhH>nxnc1L z<+<+ps=z5VARZDr@@$(Mm-jqy4exjxQ0Oh`(p4rvJ{~x#b$JL#2Kwb0U-tv<*TxT* zs3AaA?X_4nL|xdWBxRrQ!-;yVUbF z?jb(>`pB}xA{B_UWv{lhdI7#M`89QE4B$SiJBH@I2V5X7U|tN;o4>baae`L?HL~y2 z<(_>ge`nLYG`0hQvDtdCOdg1OYCqlwX91zvzvs)HE}%%Y-QVcnkLFi9D0=h}5S7&K zm04W|($A?E8JkW6$yM>xi4|2qmM^Qb*jxrwF40?J_ZpyPTy;6K-V>;PiyjrCI*yqb zoGz%`2$Uz>AL3+w8%j>!jHTO8+(s#Qa>7UP?y5n(vUt2H` zDozI}ePnYd*f(2qwGK)exIE*ydf-0?e<+N*I$$3(2z zD;tUQ<;mi&b2kFv$Sp&^x7I*4Q|-*PL%R09+%K&E5qFA4an-;039#~1ivhn{Ah*UO zy}kDpz31uoJu?e%D@3hakq@G7=^=l$cm$MpcOUVtx^j8rW1Y+k3P6tT(szv*2Fh5& z$CDfbAW6=RBM+eY%6S_H)}!;(-g?PrK{aUzl56CRpC$;|Hfv0a}#74;S8E{^iB=BT35*;RnQ zJHF8%c|8!ui%X(+L;?PL`?}~YKLJk-mraHzJiK}F_2AP2+Pey_rpK1@<+}iAM!Lcz2ggz zy)S_7{NF%muMz%0{|?yxj)syoXuonKEgc*MfifDK+V1iW@nt2h?R*R1V&Ben*mDHP z_EJwoWm37kgICF$2Tua_>6EZf%6q_mD%jzZMSx;j>hea$14uSE2Q&5{eI$L++HUMB z$}4w~`<3gtyl=HZwsQA?*nG*;Ycs-Cke5&PRztuB#ogxBA;0SI+c|Qt5s>EVC7npL z0DR)-m3y+k0lE6x$dwi&AiF%8y_WI;$RE~O49;=`Y?adLte8{W$^EU{qz|FGW7L1R zo9zcwt-g6aMF3=@Y>KdE6Oi`YUH9ff1yD${uf13;g80%nOWE`Z+V|TLTtX+8_f^a} z$+!`)4B>k}r;z@5YAEUZ7S$2Q29F{8WWa`RZ(G-Sjmz_37x21rfx z6=h+757;t(ZmbMQi-*iO%~C-5a(7OiD)I*l<;<9|T)>1T)4nt&0m-UrRrK8^pe{G% z)^A4st?szezNC1>L!XAdxg;Q*(C!ysx)JafHv~+TDFVeaV$G)3S|Bun{hpnb+(}R6 z!+O1_{tSu9-Pn5vaMkw~hwiCExN)mLc2p0D%D43+S{#8ebSq|4H1ZWj_LhTs1>7l3 zm4R)(_kpxJJDPyZ0qNS7*_rg?$Y*|1YtIY@Y?+o;r7oE}m0s7ZW~qYe@w?bbk0~G) z&K}t|+=9AMo9fwtdNA^xW=`-iuxX zIe~RFEe7$R9hOR6@O%{TDf{+Zi$wn7jo~)CAFhBuc}2ot0mA)(V((v4 z4}s!Rd-&ogT36C?uS)CZKv4_Y)oXJWs7u|Gu#$CX{zrU1?YPLDEIy!9n`H=u^vi|A zlp(|mZ+B9|kPff6Keo~C8q)tlK1-JMai_w4Zuds5L-n`qz|KwnK#*0`*l9Ejgu2y3 z4~q~lt{uM4D@VHj+Z%gZLk}R(JS{$_@&Nm``Qo~&r(E8x0Tu7-vw?E9mbubo3*z%x zj=#!L-d97zjq;yBNtsvchyMtK{TEMN9y!aM@=96LXpHh-a=0#a?{y$tI2L1l>kD8v zWe!}CLAu{3D`3G1Ex_G0z~78S^=~gZP4x3>?&PaA^5!lTfZuvpd;7fWfWOk_^oY0s zsKkq#=X@UrtnApG*_W09{-^vFwG+nyB;>G24UX@lJ zJj|UsnpdDaF%Jk2n01<;!vULnm%eZ44iHaRjwNOyU2uKLh{ty!#DC30<%e-V-M>6= zC*#-jet-5}b(uTq^QQ8~xk#iVI#+l+Dg~S>aY8rYBGQv+%nkJ9&T|yq^=={}M>+ zD-Y%5p!#xG-l27KDd45|M2{V`07}zi-nJ!{2=7O#h@X+)X)ffBWFa4pm#Wis>IL%A zxH;>ERwDkfjy$*M5b{?GK85^7yk4~S>$8=UfGczOTD39{h(b;wjj<%an%11ZaOV`_ zfsN%EXCDFfTYy~g^%0P|GV9h@3Ziph?~dG91cXR$Y0+3TpQz4j4|Zk&zS>}-O4J{y z`%KgV%Uyu{R!QkcuQiv~E4x97HXn#~h2?TJ+CcgGHaTe7GQdeZsxl50p<8-YSpDIAgb?@z?R$xO7)6pa-J-ZC4NKf#iOvxirBJ>F7tUYMIeoUf0X1+?&_AQ-)V4SIA@BDf*@OEh$K6 zt!M1fU4rse+M#OcR-`{YvCxm|Jiv7^zCW}=ymof|?L6PzKv3#=m-wlkJK1TK?l2ee zxL5^;pcV|ogs;y}axwwGx$*q>>t;aUB4UJ!cBY+c)(KXQO)mQgZzw zX-)LrKCuVxx!fs5(xxrlDL_;^TWYW*lgnGN;qj3(7Ngb zvJbop)C(I=y6;AO`)*J8gdnQ(lE%G~SE7LY)BMKh%>wFR**a8haEeqa|&?V zv_>_axB?-N8|l@Z%Y}ZBHUSk2wDR#428<9$(DlOu#ZD~EEy^6o z)ox{6wzUqI&MsF~_!eOH{9nJ{K4}A)hN@OOsDyb9s5XUe5LC`Rl_TY7JbV z2B+0(?pgtaB|4F^f&N?`MMq_H)gtbs%i~M=OMQUIINR%-jr?rt_j}|PR2O`m=?X_} zfSl|VdStN+;P03FG4?tG0Y{l1DUSTjVVN(+^GkproL(d(i1g~2*}p6NHv&=cUZ`Yg zArK5gdL>HHej5&-9yi$s}1SBygJdJJHCp`^AQ`OgyVteLJ!~dNf;>KG@a5**8x7&<*Gxx z1Q1o3MZvpEfV9qk$r80KKzP*Exoq!Sqz{I)%u>a;Jcm@@U)N=TN*i*%G>CA>IGa4R z0p)Kk+i*hqB;dq6PkCVCh<`FVaW)kRddA4f(~{7WkVHl$mbg1Z(Z}vYH|Zgz}otP!gqj4EIN4RvK5fXZ%IVW zAnxRc8ymiua8X`7eU{8p0&GJ*F<#um5SY z3rJO)RNA(=0(Sd3!9lGR)wOqh3rVd&?B0F5@9SG2pXyB9>Wg&F?evhi>pg&fyzBEW zMQtFSx&3N(IPx#DJ-J(y8j!xzw=5xXfLO4Q+m%9tGm_HWedQ01(4JjvwDD!{vQC6`XMT6k7i)CpYVGz_ng3 zNL_;b-cWvcrTG#d7YrT@b5a5dW6EvT))J&6I(|0O3;;jtVBiSv43ICqz2X1mEa28i zkCd!L^&~0x_|zdmz%yg!9C+OT*hOdSQ{Pm$ykfNhk1#>No;G-gCk%3@1bVEa>kw~$ zJk+akSP`)9YT*ZpXMlKcR{XOXEmYUsB@do?&gHc_FZKvPI+}S}^WDTkJ4<$SS z;`une$=~sScMWXylko+Tj{Jj$6-+L#z&ht~J<|QvsxGz*MF8t~=~%eO7H}5z_na-_ zQNP{`zj?nvyrz5jt@I}#U?W+qWyl}6nG}n7A)dIlu0&|kk~=jo{nGMFo`8RvyY$9G zYarj%@I6Kz2Xgrx6FZf%U;nWr!c7+TI1} z4FgV?NvRA#`6OHXpul(`($BfCPXq-5#i07FhB2!D?CPxjb2Pa;r_&NG8$5v$x^|ZA z736Ev@XpOS_qn`A*!7Bh3#h42&uxD@4wO$$KayA3aHp=3vkmi*&)Qx1+s@@Y;ElnV zq9_X3d!mBv3I`yD82(H*Pyk}@tFynAS0aCA{PDc^KA`BGv+~8rfX{qB5B#4Zp8sxf ztDpl2seX@M`YuGce>%tNn<#p2$n!(W93Twdn!xe0@Aq=`Vqt=#4pOS z&iw+VK>nh6Z1X8M#OqPpEf;wMcIIfdxkxMEw>SH0Zj}Nm$EaH`*a+!T6P#xp9n}HD zBfU4Rfm9fB>##SfcdjoQ9(S;iKazc5x4R8UWgAZ4Sl0(Q1Dan#_dLMuWgG8YjQm@T zVCRB%3*?KUR>m)W#+_O;73bc22`Hk8cjB(0ytujQRBFdVAo`zMMbh321Rujsey`B^ zYAG*T;^vOV70C~nNCfh^r9sb*Ujjl-`#J{a2H@zOL793EfX};g;J)KFzsa>MLqMtz6EuVYsAP?yXlOAvXmw-=BTpoEn z7U}VyD!TO>fe=gCqT#iiJK3~XH!yt{P*w@G9LKE$9K+xNNmdxBg2f@38^Zttjk$AG zn*h(T##OED@En*aoDxt+=usOdy9b7UYlDAe}9JAV7Ey;4W4QC_G{T{s(EL(pOEuOkept z+R5Qg9wFx_d#&N}@XE6*&C$9xSbfb{it3#FhcAB7T%?!I*}Vzv1B&Hgj)EKV;o)ww z{@c6Iedp6=0o7cd;du!m^Ju_z9KSYJX$6$@fW8Lbejr~f{3WUI25_d`-xX%lff{~V z$j`F^$j=uDpNkp+qA20b?3Pu4g?S90qu&Hd#HGdbL?_ zE*HLo^laH@wfL?2K-qc0&rn1NC^uEoC8aQ+B&sC5zL|pj!HI-HHp=&w4+|$0{D8P@ zrSrGeZGc()ayhw11t=FvhL#x~LHhC8E`bgoAe+sT*y!iW<;8n`9Cls+%&mJeU?ObD+5f>eBjKrQF`iUr1!Ct(uYx= zIc?dQAQB3=`Z%kk-6sH7SoysoU^I0M$!!v2_ueDwoz-f$TKC?+mSKg-xaV(Q1l9{S4V&Hw$d z+tD8==J`a~;S`_<@;r=3QN2nlKG*(=05;ZT0m-=X){nI3iszomjTh>(D=g>SAbMbX`nMrk?zag z(YiK^JGE{iSMggp5Q==B?z!TK>hFGQEzNg8`mO1a73PQd=$Yt<;tQnj+cxN2ea4+E z_UOdU9pX*~dAj$9Dg!y=?z&&N4L}NLG80et0t9ak5aDynPAqp(qTUGPd_J>w%!Ab^`&w42V1Ds&r zS@+i{FVc#0pM5^go!mJdB-N?{|D zStl+KdNTz0-7gDPdn1365-TjaLm4RZt?J(f?E_*(@KM74OrWZHea@GnaHrbiAI3eC z0^(ccyCGvOKCq00r-~8&4;K6&sJlHNL5z>Ti!cX zVtN|L9j~1=aLE4?ovYUj<|Djbpk#(4Kkj3q{?&s5IM(@8n~W!br)E4@bn_s}uMxdd zIY`&n3wC}ts0V7yE&NBTTp)c_>T{Td^xgK4BgO$HK)m~Mw080kP(~~AHXI)S%+uoV z%~_6!CpSxp%tyYm_M6q(7{t?WiVt3$tqR11o}H_14**WjaOu6>sK2}7Puf08B7bxE z^7BQ%xxDZb`(A$Q0^A#2RllJgz~-j-I__V<<*~aner%u3<-K(;6B(HWDjskTz3)24&;eIDyYGFaDd3J()aGTU11?8;v7u2a z;AX9S9{!XCq;%23hU>2Z-}9hAbrI5AzFtR zd6UBg+GQZtt*E!Zn8=;lq8``w<^oWi!j|MoqWY*g^;}Fk56I^kyKK}~0Zu*YS_q)? z9C&@)<4y?5pYNoUpfRL}iVVnITY-GBHE7mSJWw}oOMEYb@_UW2>ybH!0CWBtd`cP* zgbw9LQ||%g(VjLpoIMaE4)wgfjr`x>JyVR82$Wk3{bGD#fNB*(TR8{q>-m8__fpXL z>Z|ufYX>2{Vz;}CY!76^9ZSPjqWabA5osf74w#aZ-Dj^tz=^N2)wqhj*RJ>Y)iBQG zQHyUTZB0S?x>?KJr5e?*1sHo`9Ec}3gwuaX0VV?pp+)zBWMvUMdf+5?lJ{8M+~FIl zucqy#3Pd0sJ9s@##e+NbK8yn#RIeV(muWd>0eREcoW|n0K#;#@Zibr!M4s5AXw#iQ zIrV$J*`Zyj}mxr79m zYt-YcPw{|#W#64AdJ^yrgdDT?Q$UJ-;S)dZ4#b~uaieMjkRf>F-AcN6gKL8@cT13=omr&S{14tLVq=lAC`tATJ$tzXRSB&yR5-$NcJ z0>ODjeabNdq*DavM0wlY8xzu;zTE%QQU5kMCkg&pyF7JtP8EKxk-t%~!w$?pEEY zNJRRqkGW8c0EF|Ulk{0ffx0|kPWu&<4@-8MB;7=Om?diUt9JAgp zp9h2+HjMXU>wxGTrK&T21@UoY%GV#KfH=`5v#+in>DbnfD`p9Dr-GAv_j)2fw)rz1 zpC%53iH1dhTGck0C6d++pD0zSLo zOU~6tKs;`^lJf)knDCmgtEta{n4#kHajX?c#U}G*j>G^-WQSLUuqM*u+coAVq4|_Q zzEkp95QqmFab8affGnnwD5Dq%)RTK0w@FzeeWUHgDgOyn#?`S{OD|N%HB8N=&H|NH zbzO5l^7kz(uW#VI0Af$JK;*g!Am?>_Z8DAkTwa?XGJQ~l#A?&Qt+oV)l2Ku3d`FKkq-U!P}(34i24>nYS$`(a-huf{^mVEC|OMS`3VQa zujf;s77jHK?%w`IO7W3$V0LfPWybXZlMPDEpgMbkyz# z@)P>+N2xAAPH$X$Bd7%^6JvKwQxmfo(g>SjfMf_AI*5 z43tB|g5;F@h^OXV2zcQD6l2lw>k?If^;&j5*8L7d7w2bgFXsYBdE%6v!NN z?pq4vVN9-e{w*L*h7sOw5C)v=V(U%05_f3C9+9@EcQ%W*FwH2sgixat8gFuwJE#ESQ{L||1z88mMk^f2l zmea3?>biTzgAKBPyWX!lpn`nLT{UCxw&}m;<6N`fpIr9q;sEAU}A1!Ig%5%N*sd?1zs5FY&DX-1|zTt792Tg*`x| z6>Sr!)CDrf)Avwi6ymkCM)MX=Ap7cSMkVF~g**={b`%Hf9T8p~)#CEr$;lFgl3oAu~e&V0b-Yj~xGhI4tl$4AT@_Hrjv-1hz6;t7}W$9bL$3vHaykRK9)>0KnY=VZl zFs5{?0Ks8Q-&On@P)>xY=6fvRPF=0{TwHOHJN00721{=TkoxY( z%0lk znu;q1oMA-w`S2Dr-wb)y6sq%~M#+@bvOqj!ySI0c2)Ne;njwwefam>qzaZ%XkW@5R z{5UKR^@*y&L8FM@kV;>oKzdd8>nKe z^DbI@fmj@)<&d|4JEg5fz5O8=C{YC2JR{`WGj$S2GdBS)LjPT4s1jf%CBDI_3jsUg zd*vc23#c}+T}`yRfY-Pxktk~lef5 z3_>d2#3KD+aNR#E2i4aV$**se16Af-K=Wu25OplFQ%@!VeoiPs7mw=AM4t4$dpSTB zkBRo;Aswuman;@d@uz9yO=sz+K$0M~OpC%#}`}x$8Bkdf8mMtS6Lu`xg}6o z!t0N)knb7a@av3gAD5Sv=RdmF2FU$yEzbN91&T!Nv$~Vt0Do)VHl3zepcMH9iLZSP zq<3cZv@w*wA;;>VcL4CewM`Ep2}aF)cU)rAXD+X+dbTHF19$TE$-QmDYq*m!b=z5W zWq|8gj*r>l1*9x${E5O(+{uwzBgvYzKrnGV>W%o5*pQL;Y*PqOHIGjX#dvagI}gg9 zTx7xJjVRsWUAF}6`E^R~@_xj_O!t(WSX7^re{0?u2XZ{4R(WAPcQQ37@0NEC5VG8I zfBO#t$*E2(RvP(&qk{9VmFxjZ@xxd3w<3UY|Hbv>c}U+Gj^0=q9RbusrNK$^W8A5{ z_49^@9DvwwrHhUXU8Ll{?uP!yB#T z0RFDbbl+c1xc zbW&rU`idkTck)o=h$Uew@+HQlhqAqa$X@u>#?BZ>a^4@T5<7rAvPJuQ78$U>fr%vC zZ=me8dRP#(1MnfEEgpG-K=r@7bz|@@ASafuudCb-Sc&nC!7oREd|z5%|B(ZLlaiMW zY>@)I)_Q@er*FCy-o3!Z7ZqaP8~X*oA+)z>c7OLzQhjjOCF5x=~)J3-AccUDcV3t zNoXmOM)|Z@{>iUARUl_se4!*DeqaXpUyl$)bs`<#<0p;y{h~&sPcKkuX9#IdE`aGA z$m}250HjgVa-U~|K)AH2#=!Uj;09LcY-mRQa!0Apuva*$6ZNbwYqEiIELT7N?I3rm zXKKmQ73YCiv3gl$ur12_9+w9lk$nskASiWPY(Tt977*;i?cEBP zm72OCcmpO@w6u9}J5c0zwk_>j0(iGVQCr7kATN4bE8p0R^!$m#HH!`ac`)FT>_-k@ z7t{)#m4t97e`N0~Qbszs#QdkS831PEx4$iL8N$PZBi75l0-?{&w0UJes$WMrulybY zUNga?>ZT1)UciCnkLLnDvi$c-%kQYZW~B=Vae=D3)zeJo1K>o;E6lea1(NB)&*@bN z7ovW~LjjM0DD>ro5Pc62x&1ddU5P**nA93m7Xy;_TepuK#7{}=Gn(dG0P8rbsq6O= zF!eiIF$a zCC8n(^Bn_x!kO@bDRZD^TuwY7ZG-UF{;T{7(pgHKLoHrM(LS`1hAtNZg~YPI(`gUb z@Zq%u;l)5yD;FMYy$a-FQupp4;PNVbCsg*V0P3a&pMy0MfRTbNguQft(w-m{;m{8l zXY%Ld+&C`JrL3lMvoBy;PaL-PNFd!JJkqhp5QwSdrqq$AfN3ZTD!mN@%IkQ;BDG{7 z$ffk1;5-7Vc&X8|C!s*yI{)$exEQVd=TJTK1+-+xPWgv zQl2Vi4%jS->W z@8`(}89+X&OH;T@MLH{T82`)?h_4)iZ;EX|>+$<~dp@e`?mit&+mMg73y@m3@+|Tj zR~r|6ya)K3Pakf%D-QUL)Z6U2w#YBmM$=jcfof!O=vR;=5c0d;?-IVrog8FK=NtHP zd97c4s$6#h`BL+bW%r8#fBWiCfoNNI=vq0&$aX|wD&-~6U7Qc^`5ZcUiq?m z^qh^5L}U6+z}jMSn@S>4{d<4H4}XF?HFby4{QM&t?__xCdLf{&e>i>_?FQmnor%F9 zq+dMZHH{k{0%76hdCK*}Ko(TSdQ7VZa>KhF<Og&h^=5_)?T7otIxelHg7~*6c6Id(55Mryp`@ z$t>irB^^_ka{*g+UVyEG&hvul*XZs~K$#Nh=`uY6ls>chU02fp+q>e_Zelm!kBi$^ zkk$g}lDf&r?^l4GkX9PnMB+{r6x2n!BYsxi^~5o5CwD4poi9aJ1wd$UdFXWJ7T{Y2UO(@d3nY0=)SIymu@z3E%pWGK`r?ms+#wQa=FQh|l zC(X2akzO0UW}@^H@t)Qmw)?%tmRgf{oXxk&7itc>k;=ZkTtsMY1MCz1c3 zyr%mlMG#0O#83U=MLO*rt|T2_3mGVyygP?lxJl*>8+Cf^-M zDn|TRbFDa`>nh-JW2J+8gn<t@zzUac_qQkk-D+ zda1h(NU~x!XP%>W)@$D4_cwsl^Uheba5<2!YFa9SH1dmBR(}f85sboTy*a3Ec(Xf2 z3-o~eBWLAiKe zL~jy$Zy$HWbqx@u5(JqQ&4}NG4rr`>4%pS`UQ&z@kbZ3X*xIHCghj!`@su3yWH3{C z-GOI7R=ir65sudF-G@E@Vhsemg2wOl*+A^~bLm;P7ch?oi{`RIAmq+<+xx%*;pr!F zq{9wyLlaJQOZ$O>uWd`}`oQIF?bs^Uo`dqErN{PWJ&;R>cNeZ}0&;k1{gV{5F9B&1 zHeZlmcN*5zng0Ew^Y#&<9tHwI&~B}_Spbl_JbH&&Ymv?wFsowjL_S0*Xl{HV-~t@3 z-@KXy1Zj;)kGg$8T;LkRs9p^Cl#q1Hx3(lDhOCX^rnOJ^MLSDE>*GZAeUE| zlUlER9&kSe)MPC$0@m*I_-a1VbrG%Ce{1IfwzYI*qW}j8YMC`ev5P>~P*_it+zB-M z#0}$b-2rpoWMRH%6sQizZh1xLqjL>ER%+@7*iDgHuMV37e*G^EzqYr4w_D^VZ-)c? z=NDoxtAc?hw_Vqmpbz9^YW!|%RG+q)y;r)9biqqcLe8vJfW__$Tl!lIh_ei&MxC31 z{Ov&tiGlp-$jGw3uc}CgzV9)&;37R-^GkDd07&uSjeRL(z{jjT_R?es>3|g-?|I3{ zH^s&@*D|RItyq_jfo>^UlC90 zwZu9J0Qtp<{4+>yjxihMb*Xa5!_pbQ<)q-&u3>^@50m|zW**jP;)^C}=m zzdRo?I0DqFdxtyPk&d=~C-(8eVxX-WrjvOHKMG05(`>tdMqNp%7`XzNLD`pR^I)Jf zuYDKd@Ei!=jOt!~mj{y8=rVsi2gn~)_pLsKd<}D%Pno+X(4-um|CodLX=>l$HzGX1 z&whTpLuU&RMvuztNob-v{?6l5_XebkYZ4MLA#|?##|xwLkgv|Cp|$pEm%T+t%P*9NtVFbvp{$^(7H1b z@dd}Z{ZrFfAcc23wQQ{i>NEDM5WzV>i&eZ~xK0d+r!+_A>u&(!i-bY#x*b6FSF~BO zE(j=B+|N?%1c1o!+?DF62t)<<=C-*7K+ssQL&-uNs4fG-yvvUOKT5U?U8)9@`K#v6 z&r1Q)?a$XtbV`sOm6rZlsRM+ngCW~4enoXWU9~%NA<7Gx`uJ8C!uYy*4A<2jTj8j*hCrejr@C z9IPMd1$c$}We-vQVpevAItBrVw?7;?oh%D9Ba(vLHd7#RN=n6VBi%0&!aCH1^7i8b z+45J5f&66e!pb{i$QK=Wa_!+wAU8TmMRC-D;$pG-L%kM|+a&i7h_nM?$5#KmOFw|T zZfl**s3Oo98v|<&HUYNnlbOXjw0}7_i5-gdKoKPN-Ky+|;LmkWqO2cN>!?b1~lzQ%J(p?CI zutm?qfBXjgGQAPGk4Q&u&BvajQ1>{rrX{Ab%zS1 z^+#?y?lA%KveM`Kx6VcDy!-PMEfui5-@zl(dD850rqFf`Phz}dlPW1+P3ZAn%5;X zNCOpxl9DNnh7J`aLqf?=#t>4dh@zAtWk_j~<`Rigi6T*kq(~wfL@0^|lF0w)`JVUx zz1#EMcXz}4u5GJz4Qs9QT<3Y5`~KV0brD{7ZQ<(4z>2vi8%rW5?P*hYIvfssUvhkO z@Hh0|0S6w}-vn;1@(2+^fc`uqD>Mez<8G_gmlLCba19w?x3e2K>fza4cd@<}ZywY& z`7m;v_0kD%PvdwrT^d`4`XXhJ8b07DP<|U_3|FfIrJSE`-tYi;Y5SAM`d;JpWTp6I z8^(Q~zMwWg}7E}UdIXUi5Kjfb-D#{yJ z$A2_muDA>R@x_&hDi434RuGHhl%s&`DRPwLZejk9x#V8gjrmS?4RJ~o^EGk0xT76t z^V9Qbdt2Zu1&+(-Ab+Q8QOB&&_lCu_&-{RSLXh-9N96?wW~(&lP8$Kl$EKmPcMd|m zow_D{Y#OkKzxZz6{tSpy)i1Y6rvO!U@7r>L#K z-o7{K;{^1}Dzh%9iT8m@J^azQ4*g;71@i2D8uO0-`Ar3=<3&ru#rk7CnXWii-~TaC z7SESN5_p5g)sMr4NjwQ9O_SJ0~H03%dw4W2mucdiW4#oT+Fkx*|g{Mv2X$KD^<2{+gc9%Lz(MbIdWJ((XfRt1Av{- zpi!9OgStBtG_NKAr!~XmYOf2hpJI;KSe^sH9^V4-O7xEhOQ)Er9s$~C`^2z=wo8F1W#jc6B|$LB z*vK$?Kd>zyZX1yMiejJF;uQh%AUIBgl!7=UE%YpSftJpZg0=d$g&wP&DRTngMRw?pT zfq_|GkRb@vwUdRTG2hs`_fDxX1Gc@NdyGpo-tV*>_omMRs{eh%8*0ea&26C_k9vR# zSRNX^Z4EG|DWkj?%&RMoS`E63xlM3qS6tzE;EX7%t}AnZ4u84OHf1U1`d6doiicx8 zYtd2a-XJ*QfBot87GU?MY&;O~80fXmd*hSQUuS5j#U6MH%+qZnPKdw6y_BhtlnT&^ zQSu@59Uyx0?(c5N1ZttlWSh1JAec5~?ZdSOz_p)H-qINb?DEr#Uy6nUb@FjY;rS+D z)?1}?tV2CsR<1G9Z3Ey+6{vdk0qxf9?d^p*-e^yH@Qj;4j2JaY@>&+~5ernGUqD~- z8uiuVpwIXJ51zf_`~UlXRkLHp_wV77r`04Ib%DHXG|xol2ymaD91bw0fPdw^Bux(M z<93@yvZO8$U48Eqj>O`;H02UANg&(;g&P;x12w8*cuvqEpj79+PI%HejCTnr7ksG2QF!thXuy-!*XSzEI5l zMD3p39bzDG=s#NNvk5Sx)Q#e5vrzY<3WZvbORQ%5eJL6Tgw2H29Rt?^IrI6_Wkt9? zVs#Djmz)7!j@va0b&-7iN;<-884%~yb_XxQy4$9><@CuRAPCh|nfnQOB1BF2%Ec2P zP+9KhT`?c;XWaancgVG38CCCNz5ruvozTT02S0A&b2gizugR`SEnJFvn{?Wx26gnP zOW6gDH$Vg%`PbjXT(IHD(2Q$1f4)St!Hvg2+D#5pp7sE^dsC$MnN9(sV!7-Usr$gS z+84BDE(YSdCUkFl2aNyLr=8-;zzkbBcjIUd=-oAGcd{`Dm_L1HJa-+ivqtV0yf4e;AXnH2})IvOElIrsbLu{pwEb(sJJi~G%UP~Hyo6NRYm6!h=a zjfY>^pg#6}kkB|)1p?LWk(u4nz{*Urx-F&+oW#J{&E*Ba^jlKFZUmqeUSyBz7YnRm zPyFsn=u@HfnOFB50IJHWLDv;IczVhpGsg-L95r>F=TU(3RH<)!>JShz;TMK2L0a8#ieNbM~D zyZd5*v>Bs$Z`LUw<*dDztiKOLU5J!b)gGW;C%-Z3WPz@r%tK4(?%|Py*_TuFL4b;>7%f{&h0b3`t;EFMh?-fb+ zO-880I-f4y?yd#4q)?)1DYhGU_Vc-Ym~Wp|Dm=e49Ei{sp|TnU;C0+x$J>?y8}L%E zrU-Q-A!3k(y8}>X#I|bY6aaC}Awzi^&JS~O;xMg^-@jMBd*@-~!aG}1lD9elVRXc3 zj`|58o!$=EI}bV4>RyvdVge9NIY9|?-vYDHv%ki>H^9o;f!l#AKrX8Z{5Wzx@=Mpa z?i5kL!Ixt>j7O-5I1T>1&|@pA{uKR0k@{_rPe_SQkj+}ip^>HN-;QKPpIuR%)aGL4z-N1-Br^rn(B~T}3B$)Ur0I5G}SVAAx=VB*j_W^a}uoUOn3zU%4hvl#Bp9rM)nwGXNuE0N$ z?cC$w0ep$;!Tyi2y&F1vzJwh>ejYPd{SNAov#UP!k^(AG)?(k{F~|Y7Ym1zB0h_Z> zLg@<${PhmMqqD#p!@imx>T|Q0l$2vGuaO>(atm}1;0&_Nu17_wMcm7KTkhUdn zj!A3-fsjdhRuTGmb>^;ZeaC_Ir2ETXlmPBjTg)b{BA}V5QH_l=k>l(lo?JCRz20c+ zGZ6Fb+S42F!8)LxrA+TXR2z74x1@$5}S)X+bhTae zQAr^nYcFaH&O%>UFT8Z}2;|fyU1wv)Dt-TVr-iJ<4-fATY;3`7(A1z2I`pkYF`cH)wvGWbrx*H^>uN3X5n+7_{@sS zLlS@)WSuMTWC`quyOpocqwlU=XurI0H1PWGQ#RXP2VQdR4cDD1z<%wo>^;~Lm?LNO z=owbPSUB*zRIq=`lx{u0lm$%cwz-B^(655jrYdUofnfXnHJ41yff#3ywc+%5;CsV2 z3+;IV0`aRkAEwF!J6yQ!F=T=J+K_>e#d^Dm`BKSsprN{-|7fN%&@(b={8`ezS!w^{s6GUw5refr2?IM zz4oXk_NS}j3gMLu+}1CL-5%ojt_pf@#SH{1D`ekw{w(H=MA_=$=|Gvet6Y@70s>;t zDv{g`!0y|$dXon7PRPh*L(JWQ&L1mOvtSfZtM7zFE%yORys#_&>rJ3Tv&}tI?jh&q zqqy%a}cWj=az8`aQTI~A11;_{8XVx7$i0fb4Zse-okLWv$ z-Lk#n)sB=a?h_vk zka|}Oq=n@}^#K=@Y+>&zim zEjk>>%b|Ym`cPmtKJptHwg{Ne{lSm?4e&mLFRc*do+H&SskR)+4y?C-KSNkr#odEs~11jW@ffT(M$TW@0-oxm3g%$x?4wyFwuH4lyJ_guhNhKSY za3GvV2uz#AfLc}WIabjeSe096k|gy(Fy)!>Iv3>hfv=TLoLU6rkVM&}FTKES8a!b+ zT>yONTb9}N1_WE`dNr>V16R|RsUd=SZ}(82foceptG15b#_0Dx0LWKz811N?!L3xG!Ix$887zPERav%YuWzY%7$Q zu|^O0suxY`{PqJee^E&3ogFwniTUS`|^>u2s|x>HvO!Wnhw;JLVmaI&Deh z&lfHMB7swZc73;c<2dZrG^*cc1bnV|pM&`Y@i<^XHP9T@(j4^rwzBbwSmBQfyeUAioh4jb$8uQ0&=X!qH&L2VQz-9fp<)S zHZJZdu{;V?VdXp3*2Tb%soj;M6a+N6UUjr5=EUZg{Z8d+0j+z_tW2E(I{Bfm2$)*!P zZ#<)t6a5rur*-9%J>~&_>iv4Nf!l$&+usFRY-UI60_3Eyv1$$1fwDcF?BVDG z0&fw4RE#4qE3778Tr?gSJB?kk-j{(foKv(kw-mSu+56u5Tmiz*?RJ!fCvdw~&yvZ` z1V(fGtNUlyARp|QK0U(~7`av3S`MZG=aBJiP=F$Ev!x>&3R-~i$(#Ds8}EC)`1w37 z)Q=RAgjKP~UlzkROfdWe?6xNl&$}GKx@)LPo6Q4dHGTUFJsBXc9S?XW^8k2N+HYAx z1dxH_Z+^{3-))H>UDG`RSdoxflIc@`(e~S|{SxbU*6O`iM7N-xCJ$S3^85G5np^m~ z?*%@JE7FV319qHjeM1K3`vGkS9QS_)!MuzuJ#{_6uyeBSmKY%)oW0y&g7rUVX*hvA z#gFTbtklM6uYE>s`0M|9&>9!i#5t zniAv^w^a!kmrW}?Uf}xbCH4Wc#6D|`qaCm@1|IdNT99*eLZmZfzkh#D>Vr^Gpx2!{@*v|Okkd@Gsn?NM zhs`fmDysl<(dSxPOFnS7M^Eh@x&(N6!LzXe^_Wv1in)K|fqs1N!g0e#z;}#%;97hg z^H}v*<#+KwI9?v_^

(HtaPpz1NNT^Wgmpr*IulT`gSD4}HOqjVWkBP6+ldb`NyG zTp4x7c!DtqX2nn5w&o;qnZlsziI@k)$_E8&sD1zW+#sWTIpBjUd#_E51Y-6Fr*Cnw zKn%Y*!oy-6kSoK-4=mY_^RoBK0K-lYOxSbW$^R6vahei#A1)yuxJX$^pl>~#)^D$3 z4Go$h03=m=!-sNiS^JU=zvF399Ka!CerXd!Z+u5V|q)=nS{&sCnyYyifX z727y_9*~ZAv#yHkVQ$LVA@5)Z#G(@WFq>5L)9ojujT12smv6giatOE(JEx`dY=Hb6 zY*t&BgnYWRJ*#U25C@+5jjim)`W$NP`7Q>?wTq7?-$jj4%Hg5$%SfZ_>A?irI z-jtzxwgSCf!RcBQ=7O(1CL5#kQBPl$T|M9p6kTyov#2my88fP5uJ_EVd zxMoKAOQ0wAc=`{I0BX(q%wwwqfeuutiU`B&M_ese%(DWH86|DZ=%Sz8)SEXd8~9z1 z&J^U022MvmRaDLi1b(Bm9^!m)w+?rBevtz%C)?w?_6uOgr%Fu-e+PoqU6m0kB|ru1 zz3t2{2R`qOTSFw~zJw8bI@3M?yCL}Pc8LPuTE3)F;XLN+kzsGza6P{osliNnjlTEI zIV-*r^P$S%ZL>!J>8_jobdwd37ZhZ+kf@KbAA6_TO#p7-w^4^11A(Ou=4M=8hjpTI zNBPlJASMpo|Ih|=sD#D<&CcOKEBCB?>3a~kg=IrLnoCiS=X?s2sRgQQ%gvY_!+;nu zLQyUdbAoKnxDVEi$jxa%`#|y~hRv|7GW}#u@W~{(3y8-Q*k)0XrtSoW~q`ee%HQE&`a% z3R~7FAs;uaccVUFZe5}$K`Gm|JAV zBppb32mBMq*)Azncx*b~_zmYbjtd0?1KKGl1)N zYmlO13$SX&Us?*gfSs4EEw*tpP&P#iYbtv%r_7X3e7ptNWsCTn(4#<49sA%_C9W4g zxlk`1Szw!jdq-&^e>TpYf8z>rrF^*Ol2}8$zljYaE(`*~X5;;i+%};0_JrQm-HCOx zaBtb!>A(#?^zz7=R3NOL96mNG8hGp3j-JN?fXUh4FD>61^YXLB{3&hp!G&+i9%uu7 z=kg$r;Jd)HkJm_VJq-MvNweAsA=LE=v0fYHfIeb7DJA*_aB36B%LZ!#w>LF){w*2c z#vI&sOWqbatM;JUnZdwL9N5lbj-Y)C$(W!8$e&@8=9>yo4;m&6n|le!Ix}Sz%rSz^ z^RK_?--^05K|`YyebRb-d7L@wT8nb$fPNJ~`(N0(M8XDr$9IKKmIcrmM>YCRHv&I) zwDJQ%C6qpfgE`2!pl}eH7kHow7KS0FdgV<_VD_3>w!r)^F(Pc=Bj-APQM2?G1s5HabbuFa2cNS zx+aeXs^eSk$jfz@yI0L;sN03$S0 zv!aj$dgrYVLQ3L5D87|b@kc+IGHiiP9`c-6$hVDSFbC`%cQ$$CI^bJ3if4cSxvbct zmDiQCfJ~6RJM%Tx>o~=_TitVkcv{=SPnQO2TgBz5i&&5SPd*JF^j*&y6<43C1-gEZ z)F+iH;H>zwC4HBG(HLdA-WKP*&o*{bvj`F zIJ(&w=%%dX552p9ci;IfyfFfp8!bLw%SzGj<_}p|f%(~^__2ij6`Z$C)9ek;0nZmm zbbiSNI(uEsLBM%4FEz_mL*1|&9V#|29+(G5I;2Zce+OrUP24d9`0-crSIwA+-1o^- z=2bcfh6WQeK?!q1v`s)S>Sk!~)V|t<$T6{9Lu%#GuNZSl&lKe2>Dk9?NMHxN- z1S5u?uJ~*Rq@?bpJ5A?+?(QF#-TVSbDMsY&hXC{osfiJq$WKQ`2w%>K1LAezDfhL5 zfvD7aoUTX#Kg=;8UiS^q`@RehD;EQSmdRT}S030rhecLxl0a6C8`T_-{N`q%V!I#` zm}{?3&vD5II>M{)>V?O^@A00LWLg4@fzO+Yk#0b1+KOKCcm}M(mK$Q`i-C?BmVGZe z1t@QGC7*uhkTLe81a(bdbc;*s7F+~@`1Z?D!8brKCnxO4QUd5H>HdjQ zF{lrd2NyY_PmH)1S~X%A@<;kIColA2^H@5tc^1~8OeW@ekyso1q6e`N@(3& z;H82ya;_7=shYg3K5hoo(`2)`741NV^=TQIrvTS+c-D>GaljS1&sxB|06L~1bMMC& zK(thk8BjA7$c@Kdt#d=2-0(@*Ht#8L=N3qfI%^BO^YvD*69+&Lv&Bs!oWUINt#l6{ z7p)W7Jveg+P_8fU4Gc2`s(E@+Ty{3*^HITG$Kx?IfR< zYtNyNu3V%zP7BB_Cl#mZWdij{S=L(*9%!(!EGESI_TqC-cR06m8fa%dEXMj#5Xb+U9*h0}G;lOT`P;GQ+1&-SN;KO?-pt?Qx z?&PNdDYzh*xeWbsWQfD~8PBla`?;k{Wr4eq+1yc64uZp{P7Fy$K2wh;G*aw- z8D9<5+>h`2mI;6_sPLG%ej{+&8qWvG`U5diren+^E#Sh-SB1Sd18b2;;LV3*V{ z{pkB1s5x2V#w}3?!HU)g1K!UAu3+#a&3xq3o$kq>)q{Yqh?=Lcvku7h_k9D+CIC^D zZhY4=0@$>Ak*L6Upmh&y?XMmHBs2Zoaie754bNG>5J$g?Yg&5r<#FH#o-yC5G7_k_ za-SrJj0Lh@{zHDuO`uHm7IQl_5E!lysjc~pekQW!Xq^(~xM(3kA#(fb zQ#B_v*^nka~;>A z61jGMi8wI*-dWkq?*PgmJ8Ag{G2qQt)OluX0qTjNPT#;?z*;Ze<1T*(D2=Nz(V>`E zYqjhOC8~g!LbQGn^#>|-+^0fUGvI8O@!xK51+K>}s$#zaP(4deKfdD!T!8Ds084M6 z^HN#q<;Xp`^0c6@y@s=kx80Du8-v(D~@-BCKnvAeXit;L}GKycWTA zl3)8_QOt78>#OX2o*4qKdH&s%i&enunCN|TUkB{b{`DJXVgGNH&3)SO8OY1C>Q=aK z0e)Yt?Z#2zKvR3>=a^tlUn<`5wQ4(%9Ro}@*nIze0%GpDW|NWU-RG}DoBjRY0Cg`e(KE3D2;H5jbI0BS zYHYQO|4LC{jjm3Nv1NhwkgzK<{eb=I$g6QJ1i|REK%Xk~5eebtH;DaD7UCbCOp-4y2j+krwHGKu_-VNT2i+_-yrAix!Uo z-XPNF#kDTr5>45F@4DUod4;pcKA=aB-8AaZWDrP>>}t0ei=0+tJy>4~xX{>lgI?pj z+)=r^>_Grfm+z;@ICuj0vF5YuC(Li{@d`nfB0wx#x+@`dC$P)!_MB;8flFKNzjgL@ zAaqRXdlN7pf4;3JadtfLU8~AP6Vkr_yPdc8?KB{bOOFNY@JD}M8|5861E}hqR@Lg~ zfxVG2+hUh4<`sJ`emwHLgu3FmotT3&_IIvx+XeIxy{a=|sldLGv5;J^22{x^o1q!V z!R&|R^Q&rrS#xjWjHyvT>|Q_VjCwFo^KWJkSUC#${CWQ6$|*pVrIKr%&<_deT=S*K z?_Mn%KGcWBDhdzkAP3p9mbkNWJSo1JHw{`YeoAp^uIlZ8b3z zNI9h?kL1pPz_$H-=Buy3HjZhz6+Rq@QT{_Zn|gp4pUKL9pP$dCN2Wy#!tu5rENdT! zoVno24fRf7?isA%W)r|)H)`GVQU>@+UA?%pu|T9}9e$IIdg@p#sy=oq&|oC4V#t6% z6=G^VLV*>xGZT`z2TXD3tMuFlKzfQfhWEqqKKcIA=#XZh66|wkb-zSDv#Z_ZbP?FE z`2)|qyAJeupY;1>{ehEPTT!ZR4aAAYCW)_MKyoq}509Mze&yLv-wSF$u8MJZl#cnt zW|sQ5i>1huy33y^M*yK-Q{hxNA z>L$3f3H7b0cF61;pd>F?2nrKG5bkDsd(dehmQSBiI}7_|EiZLo?R?~(QBLVz$kSO* za`MYcfy}&gmFyA*x^TKe_1&?+n9b?!zJfdZ(<^~|6K_?%0LLkpyxJLpoN^ZebQ9*jP-BvW00@5^O>%P&G56lpnp@?aPUwl4RakNK44n)L4lRRE_EL5KIp zahY(hto79#3gt=O636%rpTtW@T8gb__6TM`L0$olpmzZp>Wr z9t7Lpo!4%<3$*&KevF_LuYaj-@E*+H3mV_w;28Alg3q^JW*`rRWqz2{2DGcN$(Y%b zfxCF-l;#^DU|#HPm@=mfs4%zXW6tdZ@PLHuOb#@`lGy@qvR^@111m>3e@-HnW0z0DOiX(IbGq+qwR}mpr}DkZ+1Tc zTB@-xcpI)S+4GL~^aFqo?K8I0yarTboSaQo1MuR<<4d}0@VY76hm>Xm;g&o_X3893 z7_r#2b9u-Yqf30HUIH1USUuwC17IVc21<<;1v1#vC_v91NQYZz#%zfJqUPzTiW_Tz zR62HC?8_=(uMenrCfg6#@ufk#cj@5#^?S5J5B+~+AG3UBJLVRd`r+ftfT`8rTJ(km zB5?MuF)Jj1d@O6d#54&Q=@a|)cYOr%g|^YjL|dS$io3mow1LS;xNNAY2xR}XQfqG< z2ak%Vfax*l<15QTB9{O&cTM~9SrqyTefpvq1!U3T_?gbZz%20aONyHgti|c$RS%Pq zOKK(S$BO}TV1>knF`B^U4*8mFhu0~c>Fhqw2Yc|shsHQt-c2M zF&EVC7rjT`omggK>c$z&-_M?sz-UNk+1bBQ=rl4L4mB`36)>bz87TGv;ya z0j+jVfeB7KJ7}&Euv_e<_MbzJel%##(OTs8VUY>XOvHh3{IKS|?sQ>V?PVWsp>-MAH?R@4IWewDRX4dO%=x>U53$=SO7b-+P zH!w*8-l8VGb;cCbiA6&aqLA-mMNen0Rs{NWtanb-+a#)#!;`xQG2{SR;3NIgkgm_l3v21imAG zM6VPDf@2Yfj%%I=`r%%0`8I39zn93665pw?{l zcrke_2+V4l#E&C~YPj2uQ@#cQzjI$_hdlr)a09iV-y^*5$dTTKsH^3j0q4(nAeTR0 z@?d^C(2EQ=U5!NDmz!!j#SnewQ2oAx)jhyX-d8GiZ!=I!+_pM?y#b^XMcQfW1Ml`A zym++@klWlUMWh`ue_e7T*0cgSQlclz!WhV%iW8cajRV$f+cD?&$-ruyQ|msb4D6m2 zy#swDfZA)6LfPe_K5nb)Dbobz$_0m>(jK5Awf6?hQ^s6*>cZC-Rrq{!9Q6g~-=@^R zd!!<;%E4xL&LJO3sgCq$jKg+=HYtS!0g>}w{%@UZ;e}{g7DT6njr)J{u22{wTFulRjYmxg|~5 zf?Vtwk}zPZCJ=^37LLssz)6S(t-qTDL|x`Q9gE4p7{86meli<)&o_QX-+!;?+b@x# znaBwpZoM(rqJf#;{$}yxa^!_(=5|^==8yEbEAlS@->>moq;VAp)IaFoACLqr|2}Hq zL>}0OVfW|K*w0~yL_IeI0&!BM<1^MD6T$VxuMEzM4(5haY_XeZ>XxZgW{B$1(3)7=ER4fpBC-cvQbpbiHz^}a! z^{wz~+4&$I&og~$psWJabcGpV<4S?=zwPmyN%w#|5xuMR`|maL;O)kCAb-1U5?A$Z z1M<<o z0d$Mgi?K(GfYMR&-{*M(bJ@dok7iu|=E}n7J+NKxwiiAsm^)Kv3=QxO0B)|j>JBLj z;6>NYcz+u8TD8IV(bP!P3BU54Gi<(p9>eu%5_0yj%j=6=J^^tiAhqO*IZ%13TCAW7 zIm1Wfww4}nh6DFLQAB=iD7W@KiN_V~|NLUbvPItS#_o;6lF4}q^e-8y&MO(2e4_YmQCW8RH@Tb1?|7~6=bQb~Uh z$Rw|p5<%XuIyqpvOehGV^S-^xZ3BU>)v}q@H-J9rv}AP1bs$g8`xftqdNyFA_popI zK%I(y97v`B6aN10B>yh-IXj1E_w|5}c+~$Zc?IaT*R~}lcmd%b?|7B+1lD1y@`1}E z0$`>SAz$>QQHSiDhudBS;$78fMf4+jPVEGbx8Hynny~KS?M4txW?!A_umdi6(6t2x zBY{dEXdmby4*czAz4V~>zz%G`=4*+(;V-HAkyQl2;+DYRBFrPl%TKgwtOYir)jFf~ zEar~*!6$tkfq7EbnRkc=Qgu*E_N@XS7wz{v=wATTUAJ*H*DHa2|ADUpXCP+sv=!UHwWjO?x&ZbwQ5&1x0@4YOwv;yc|ua)o7DVV3;4?jOC67!y4z4tO6 zh}CkDbp{HU2QD66=dlCG_MSIw%WQ$SJ2X~ntJ8I{58-G?%sMnB#{nI`DU0;9n+RvW% zr`xYw^FPPzXKL?f_dnC0AJywWZ9l4`zZ?5sS>wOj_RseDXY2o0w_jZ+|MmEH@8zGa z|2zHp*<*fIpMRu#|J2T()bY=r`y);GnS1=zTKd_!{uLeg&;I<3AN3M(W^FO1vzsf7W^4gDH|EKr+DzN{dex?pY-xaKJ~vjr@zwn-)YR>_2oxw@}IB$Oh5nX`Cob8-}Ucjp8xmPe|Fse zZvFmG)yt3i^CRu~70>*c`~Bb2kDr;7U-g?mY5zy<{>r(3a@^0Z|6ghESI6#m*Us-; z^LKRnSK9oQ`u!aS_@`_9t`_{$`25a2{jTQys9%4_L_a#`N7wz8?Ps;&Pv-S!Yv`YR z;b*-4f6CK;v|fMaK7OZfgwX%n{?F9G?=Zm6Y`=QW&(zMJ)W!eSeE-RDzoK71^Za+l z{7=TTr}zHkKL6B*{z=pSq%VKMi9d7B&-V4N z*7NW7^{@KyqxSx(Kfl}d&s^tM_5Z)W_Otc$Psjc2{QOUH)SvR@?`*$Qi@$R0uh!JB z9`~!Y^D}(&))D~X8}`s^Iv;9weVkiE{FfiJ%35z|FrX;Kg$=Qh5rAa<$r$< z`$G!}aL=r=_jLBb2j4rw{rg3JowK*En Date: Thu, 17 Sep 2026 17:05:08 +0100 Subject: [PATCH 3/7] Vendor libigl fast-winding-number sources Add the transitive include closure of igl::fast_winding_number (10 files, MPL-2.0) under src/vendor/igl, plus LICENSE.MPL2 and a provenance README (libigl commit 7100764). No CGAL/Boost/GMP/MPFR/TBB is compiled; the only new dependency is Eigen, added via RcppEigen in LinkingTo. libigl contributors credited as a copyright holder in Authors@R. --- DESCRIPTION | 14 +- man/natcpp-package.Rd | 5 + src/vendor/README.md | 24 + src/vendor/igl/FastWindingNumberForSoups.h | 7845 ++++++++++++++++++++ src/vendor/igl/LICENSE.MPL2 | 373 + src/vendor/igl/PI.h | 21 + src/vendor/igl/default_num_threads.cpp | 66 + src/vendor/igl/default_num_threads.h | 36 + src/vendor/igl/fast_winding_number.cpp | 476 ++ src/vendor/igl/fast_winding_number.h | 213 + src/vendor/igl/igl_inline.h | 20 + src/vendor/igl/octree.cpp | 177 + src/vendor/igl/octree.h | 58 + src/vendor/igl/parallel_for.h | 387 + 14 files changed, 9710 insertions(+), 5 deletions(-) create mode 100644 src/vendor/README.md create mode 100644 src/vendor/igl/FastWindingNumberForSoups.h create mode 100644 src/vendor/igl/LICENSE.MPL2 create mode 100644 src/vendor/igl/PI.h create mode 100644 src/vendor/igl/default_num_threads.cpp create mode 100644 src/vendor/igl/default_num_threads.h create mode 100644 src/vendor/igl/fast_winding_number.cpp create mode 100644 src/vendor/igl/fast_winding_number.h create mode 100644 src/vendor/igl/igl_inline.h create mode 100644 src/vendor/igl/octree.cpp create mode 100644 src/vendor/igl/octree.h create mode 100644 src/vendor/igl/parallel_for.h diff --git a/DESCRIPTION b/DESCRIPTION index 1d79c31..b76f921 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -3,11 +3,14 @@ Package: natcpp Title: Fast C++ Primitives for the 'NeuroAnatomy Toolbox' Version: 0.3.1.9000 Authors@R: - person(given = "Gregory", - family = "Jefferis", - role = c("aut", "cre"), - email = "jefferis@gmail.com", - comment = c(ORCID = "0000-0002-0587-9355")) + c(person(given = "Gregory", + family = "Jefferis", + role = c("aut", "cre"), + email = "jefferis@gmail.com", + comment = c(ORCID = "0000-0002-0587-9355")), + person(given = "libigl contributors", + role = c("ctb", "cph"), + comment = "bundled libigl fast winding number code (MPL-2.0); see src/vendor/README.md")) Description: Fast functions implemented in C++ via 'Rcpp' to support the 'NeuroAnatomy Toolbox' ('nat') ecosystem. These functions provide large speed-ups for basic manipulation of neuronal skeletons over pure R @@ -27,6 +30,7 @@ Suggests: testthat (>= 3.0.0) LinkingTo: Rcpp, + RcppEigen, RcppThread Config/testthat/edition: 3 Encoding: UTF-8 diff --git a/man/natcpp-package.Rd b/man/natcpp-package.Rd index add6829..0c5dabc 100644 --- a/man/natcpp-package.Rd +++ b/man/natcpp-package.Rd @@ -25,5 +25,10 @@ Authors: \item Gregory Jefferis \email{jefferis@gmail.com} (\href{https://orcid.org/0000-0002-0587-9355}{ORCID}) } +Other contributors: +\itemize{ + \item libigl contributors (bundled libigl fast winding number code (MPL-2.0); see src/vendor/README.md) [contributor, copyright holder] +} + } \keyword{internal} diff --git a/src/vendor/README.md b/src/vendor/README.md new file mode 100644 index 0000000..e9eb694 --- /dev/null +++ b/src/vendor/README.md @@ -0,0 +1,24 @@ +# Vendored third-party source + +## libigl (`igl/`) + +A minimal subset of [libigl](https://libigl.github.io/) providing +`igl::fast_winding_number` (the "Fast Winding Numbers for Soups and Clouds" +method of Barill et al. 2018), used as the accelerated back end for the +point-in-mesh test. + +- Upstream: https://github.com/libigl/libigl +- Commit: `7100764` (branch `main`) +- Licence: MPL-2.0 (see `igl/LICENSE.MPL2`), which is compatible with this + package's GPL (>= 3). + +Only the transitive include closure of `igl/fast_winding_number.h` is vendored +(10 files). The bulk is `FastWindingNumberForSoups.h`, the self-contained HDK +solid-angle BVH implementation; it depends only on Eigen (provided here via +`RcppEigen`). No CGAL, Boost, GMP, MPFR or TBB is required or compiled (the +`tbb/*` includes in `parallel_for.h` are behind `IGL_PARALLEL_FOR_TBB`, which +is not defined). + +To update: shallow-clone libigl, copy the closure of +`igl/fast_winding_number.h` (see `tools/`/commit history for the list) plus +`LICENSE.MPL2`, and record the new commit here. diff --git a/src/vendor/igl/FastWindingNumberForSoups.h b/src/vendor/igl/FastWindingNumberForSoups.h new file mode 100644 index 0000000..304ef13 --- /dev/null +++ b/src/vendor/igl/FastWindingNumberForSoups.h @@ -0,0 +1,7845 @@ +// This header created by issuing: `echo "// This header created by issuing: \`$BASH_COMMAND\` $(echo "" | cat - LICENSE README.md | sed -e "s#^..*#\/\/ &#") $(echo "" | cat - SYS_Types.h SYS_Math.h VM_SSEFunc.h VM_SIMDFunc.h VM_SIMD.h UT_Array.h UT_ArrayImpl.h UT_SmallArray.h UT_FixedVector.h UT_ParallelUtil.h UT_BVH.h UT_BVHImpl.h UT_SolidAngle.h UT_Array.cpp UT_SolidAngle.cpp | sed -e "s/^#.*include *\".*$//g")" > ~/Repos/libigl/include/igl/FastWindingNumberForSoups.h` +// MIT License + +// Copyright (c) 2018 Side Effects Software Inc. + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// # Fast Winding Numbers for Soups + +// https://github.com/alecjacobson/WindingNumber + +// Implementation of the _ACM SIGGRAPH_ 2018 paper, + +// "Fast Winding Numbers for Soups and Clouds" + +// Gavin Barill¹, Neil Dickson², Ryan Schmidt³, David I.W. Levin¹, Alec Jacobson¹ + +// ¹University of Toronto, ²SideFX, ³Gradient Space + + +// _Note: this implementation is for triangle soups only, not point clouds._ + +// This version does _not_ depend on Intel TBB. Instead it depends on +// [libigl](https://github.com/libigl/libigl)'s simpler `igl::parallel_for` (which +// uses `std::thread`) + +// This code, as written, depends on Intel's Threading Building Blocks (TBB) library for parallelism, but it should be fairly easy to change it to use any other means of threading, since it only uses parallel for loops with simple partitioning. + +// The main class of interest is UT_SolidAngle and its init and computeSolidAngle functions, which you can use by including UT_SolidAngle.h, and whose implementation is mostly in UT_SolidAngle.cpp, using a 4-way bounding volume hierarchy (BVH) implemented in the UT_BVH.h and UT_BVHImpl.h headers. The rest of the files are mostly various supporting code. UT_SubtendedAngle, for computing angles subtended by 2D curves, can also be found in UT_SolidAngle.h and UT_SolidAngle.cpp . + +// An example of very similar code and how to use it to create a geometry operator (SOP) in Houdini can be found in the HDK examples (toolkit/samples/SOP/SOP_WindingNumber) for Houdini 16.5.121 and later. Query points go in the first input and the mesh geometry goes in the second input. + + +// Create a single header using: + +// echo "// This header created by issuing: \`$BASH_COMMAND\` $(echo "" | cat - LICENSE README.md | sed -e "s#^..*#\/\/ &#") $(echo "" | cat - SYS_Types.h SYS_Math.h VM_SSEFunc.h VM_SIMD.h UT_Array.h UT_ArrayImpl.h UT_SmallArray.h UT_FixedVector.h UT_ParallelUtil.h UT_BVH.h UT_BVHImpl.h UT_SolidAngle.h UT_Array.cpp UT_SolidAngle.cpp | sed -e "s/^#.*include *\".*$//g")" +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * Common type definitions. + */ + +#pragma once + +#ifndef __SYS_Types__ +#define __SYS_Types__ + +/* Include system types */ +#include +#include +#include +#include +#include + +namespace igl { + /// @private + namespace FastWindingNumber { + +/* + * Integer types + */ +typedef signed char int8; +typedef unsigned char uint8; +typedef short int16; +typedef unsigned short uint16; +typedef int int32; +typedef unsigned int uint32; + +#ifndef MBSD +typedef unsigned int uint; +#endif + +/* + * Avoid using uint64. + * The extra bit of precision is NOT worth the cost in pain and suffering + * induced by use of unsigned. + */ +#if defined(_WIN32) + typedef __int64 int64; + typedef unsigned __int64 uint64; +#elif defined(MBSD) + // On MBSD, int64/uint64 are also defined in the system headers so we must + // declare these in the same way or else we get conflicts. + typedef int64_t int64; + typedef uint64_t uint64; +#elif defined(AMD64) + typedef long int64; + typedef unsigned long uint64; +#else + typedef long long int64; + typedef unsigned long long uint64; +#endif + +/// The problem with int64 is that it implies that it is a fixed 64-bit quantity +/// that is saved to disk. Therefore, we need another integral type for +/// indexing our arrays. +typedef int64 exint; + +/// Mark function to be inlined. If this is done, taking the address of such +/// a function is not allowed. +#if defined(__GNUC__) || defined(__clang__) +#define SYS_FORCE_INLINE __attribute__ ((always_inline)) inline +#elif defined(_MSC_VER) +#define SYS_FORCE_INLINE __forceinline +#else +#define SYS_FORCE_INLINE inline +#endif + +/// Floating Point Types +typedef float fpreal32; +typedef double fpreal64; + +/// SYS_FPRealUnionT for type-safe casting with integral types +template +union SYS_FPRealUnionT; + +template <> +union SYS_FPRealUnionT +{ + typedef int32 int_type; + typedef uint32 uint_type; + typedef fpreal32 fpreal_type; + + enum { + EXPONENT_BITS = 8, + MANTISSA_BITS = 23, + EXPONENT_BIAS = 127 }; + + int_type ival; + uint_type uval; + fpreal_type fval; + + struct + { + uint_type mantissa_val: 23; + uint_type exponent_val: 8; + uint_type sign_val: 1; + }; +}; + +template <> +union SYS_FPRealUnionT +{ + typedef int64 int_type; + typedef uint64 uint_type; + typedef fpreal64 fpreal_type; + + enum { + EXPONENT_BITS = 11, + MANTISSA_BITS = 52, + EXPONENT_BIAS = 1023 }; + + int_type ival; + uint_type uval; + fpreal_type fval; + + struct + { + uint_type mantissa_val: 52; + uint_type exponent_val: 11; + uint_type sign_val: 1; + }; +}; + +typedef union SYS_FPRealUnionT SYS_FPRealUnionF; +typedef union SYS_FPRealUnionT SYS_FPRealUnionD; + +/// Asserts are disabled +/// @{ +#define UT_IGL_ASSERT_P(ZZ) ((void)0) +#define UT_IGL_ASSERT(ZZ) ((void)0) +#define UT_IGL_ASSERT_MSG_P(ZZ, MM) ((void)0) +#define UT_IGL_ASSERT_MSG(ZZ, MM) ((void)0) +/// @} +}} + +#endif +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * Miscellaneous math functions. + */ + +#pragma once + +#ifndef __SYS_Math__ +#define __SYS_Math__ + + + +#include +#include +#include + +namespace igl { + /// @private + namespace FastWindingNumber { + +// NOTE: +// These have been carefully written so that in the case of equality +// we always return the first parameter. This is so that NANs in +// in the second parameter are suppressed. +#define h_min(a, b) (((a) > (b)) ? (b) : (a)) +#define h_max(a, b) (((a) < (b)) ? (b) : (a)) +// DO NOT CHANGE THE ABOVE WITHOUT READING THE COMMENT +#define h_abs(a) (((a) > 0) ? (a) : -(a)) + +static constexpr inline int16 SYSmin(int16 a, int16 b) { return h_min(a,b); } +static constexpr inline int16 SYSmax(int16 a, int16 b) { return h_max(a,b); } +static constexpr inline int16 SYSabs(int16 a) { return h_abs(a); } +static constexpr inline int32 SYSmin(int32 a, int32 b) { return h_min(a,b); } +static constexpr inline int32 SYSmax(int32 a, int32 b) { return h_max(a,b); } +static constexpr inline int32 SYSabs(int32 a) { return h_abs(a); } +static constexpr inline int64 SYSmin(int64 a, int64 b) { return h_min(a,b); } +static constexpr inline int64 SYSmax(int64 a, int64 b) { return h_max(a,b); } +static constexpr inline int64 SYSmin(int32 a, int64 b) { return h_min(a,b); } +static constexpr inline int64 SYSmax(int32 a, int64 b) { return h_max(a,b); } +static constexpr inline int64 SYSmin(int64 a, int32 b) { return h_min(a,b); } +static constexpr inline int64 SYSmax(int64 a, int32 b) { return h_max(a,b); } +static constexpr inline int64 SYSabs(int64 a) { return h_abs(a); } +static constexpr inline uint16 SYSmin(uint16 a, uint16 b) { return h_min(a,b); } +static constexpr inline uint16 SYSmax(uint16 a, uint16 b) { return h_max(a,b); } +static constexpr inline uint32 SYSmin(uint32 a, uint32 b) { return h_min(a,b); } +static constexpr inline uint32 SYSmax(uint32 a, uint32 b) { return h_max(a,b); } +static constexpr inline uint64 SYSmin(uint64 a, uint64 b) { return h_min(a,b); } +static constexpr inline uint64 SYSmax(uint64 a, uint64 b) { return h_max(a,b); } +static constexpr inline fpreal32 SYSmin(fpreal32 a, fpreal32 b) { return h_min(a,b); } +static constexpr inline fpreal32 SYSmax(fpreal32 a, fpreal32 b) { return h_max(a,b); } +static constexpr inline fpreal64 SYSmin(fpreal64 a, fpreal64 b) { return h_min(a,b); } +static constexpr inline fpreal64 SYSmax(fpreal64 a, fpreal64 b) { return h_max(a,b); } + +// Some systems have size_t as a separate type from uint. Some don't. +#if (defined(LINUX) && defined(IA64)) || defined(MBSD) +static constexpr inline size_t SYSmin(size_t a, size_t b) { return h_min(a,b); } +static constexpr inline size_t SYSmax(size_t a, size_t b) { return h_max(a,b); } +#endif + +#undef h_min +#undef h_max +#undef h_abs + +#define h_clamp(val, min, max, tol) \ + ((val <= min+tol) ? min : ((val >= max-tol) ? max : val)) + + static constexpr inline int + SYSclamp(int v, int min, int max) + { return h_clamp(v, min, max, 0); } + + static constexpr inline uint + SYSclamp(uint v, uint min, uint max) + { return h_clamp(v, min, max, 0); } + + static constexpr inline int64 + SYSclamp(int64 v, int64 min, int64 max) + { return h_clamp(v, min, max, int64(0)); } + + static constexpr inline uint64 + SYSclamp(uint64 v, uint64 min, uint64 max) + { return h_clamp(v, min, max, uint64(0)); } + + static constexpr inline fpreal32 + SYSclamp(fpreal32 v, fpreal32 min, fpreal32 max, fpreal32 tol=(fpreal32)0) + { return h_clamp(v, min, max, tol); } + + static constexpr inline fpreal64 + SYSclamp(fpreal64 v, fpreal64 min, fpreal64 max, fpreal64 tol=(fpreal64)0) + { return h_clamp(v, min, max, tol); } + +#undef h_clamp + +static inline fpreal64 SYSsqrt(fpreal64 arg) +{ return ::sqrt(arg); } +static inline fpreal32 SYSsqrt(fpreal32 arg) +{ return ::sqrtf(arg); } +static inline fpreal64 SYSatan2(fpreal64 a, fpreal64 b) +{ return ::atan2(a, b); } +static inline fpreal32 SYSatan2(fpreal32 a, fpreal32 b) +{ return ::atan2(a, b); } + +static inline fpreal32 SYSabs(fpreal32 a) { return ::fabsf(a); } +static inline fpreal64 SYSabs(fpreal64 a) { return ::fabs(a); } + +}} + +#endif +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * SIMD wrapper functions for SSE instructions + */ + +#pragma once +#ifdef __SSE__ + +#ifndef __VM_SSEFunc__ +#define __VM_SSEFunc__ + + + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4799) +#endif + +#define CPU_HAS_SIMD_INSTR 1 +#define VM_SSE_STYLE 1 + +#include + +#if defined(__SSE4_1__) +#define VM_SSE41_STYLE 1 +#include +#endif + +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + +namespace igl { + /// @private + namespace FastWindingNumber { + +typedef __m128 v4sf; +typedef __m128i v4si; + +// Plain casting (no conversion) +// MSVC has problems casting between __m128 and __m128i, so we implement a +// custom casting routine specifically for windows. + +#if defined(_MSC_VER) + +static SYS_FORCE_INLINE v4sf +vm_v4sf(const v4si &a) +{ + union { + v4si ival; + v4sf fval; + }; + ival = a; + return fval; +} + +static SYS_FORCE_INLINE v4si +vm_v4si(const v4sf &a) +{ + union { + v4si ival; + v4sf fval; + }; + fval = a; + return ival; +} + +#define V4SF(A) vm_v4sf(A) +#define V4SI(A) vm_v4si(A) + +#else + +#define V4SF(A) (v4sf)A +#define V4SI(A) (v4si)A + +#endif + +#define VM_SHUFFLE_MASK(a0,a1, b0,b1) ((b1)<<6|(b0)<<4 | (a1)<<2|(a0)) + +template +static SYS_FORCE_INLINE v4sf +vm_shuffle(const v4sf &a, const v4sf &b) +{ + return _mm_shuffle_ps(a, b, mask); +} + +template +static SYS_FORCE_INLINE v4si +vm_shuffle(const v4si &a, const v4si &b) +{ + return V4SI(_mm_shuffle_ps(V4SF(a), V4SF(b), mask)); +} + +template +static SYS_FORCE_INLINE T +vm_shuffle(const T &a, const T &b) +{ + return vm_shuffle(a, b); +} + +template +static SYS_FORCE_INLINE T +vm_shuffle(const T &a) +{ + return vm_shuffle(a, a); +} + +template +static SYS_FORCE_INLINE T +vm_shuffle(const T &a) +{ + return vm_shuffle(a, a); +} + +#if defined(VM_SSE41_STYLE) + +static SYS_FORCE_INLINE v4si +vm_insert(const v4si v, int32 a, int n) +{ + switch (n) + { + case 0: return _mm_insert_epi32(v, a, 0); + case 1: return _mm_insert_epi32(v, a, 1); + case 2: return _mm_insert_epi32(v, a, 2); + case 3: return _mm_insert_epi32(v, a, 3); + } + return v; +} + +static SYS_FORCE_INLINE v4sf +vm_insert(const v4sf v, float a, int n) +{ + switch (n) + { + case 0: return _mm_insert_ps(v, _mm_set_ss(a), _MM_MK_INSERTPS_NDX(0,0,0)); + case 1: return _mm_insert_ps(v, _mm_set_ss(a), _MM_MK_INSERTPS_NDX(0,1,0)); + case 2: return _mm_insert_ps(v, _mm_set_ss(a), _MM_MK_INSERTPS_NDX(0,2,0)); + case 3: return _mm_insert_ps(v, _mm_set_ss(a), _MM_MK_INSERTPS_NDX(0,3,0)); + } + return v; +} + +static SYS_FORCE_INLINE int +vm_extract(const v4si v, int n) +{ + switch (n) + { + case 0: return _mm_extract_epi32(v, 0); + case 1: return _mm_extract_epi32(v, 1); + case 2: return _mm_extract_epi32(v, 2); + case 3: return _mm_extract_epi32(v, 3); + } + return 0; +} + +static SYS_FORCE_INLINE float +vm_extract(const v4sf v, int n) +{ + SYS_FPRealUnionF tmp; + switch (n) + { + case 0: tmp.ival = _mm_extract_ps(v, 0); break; + case 1: tmp.ival = _mm_extract_ps(v, 1); break; + case 2: tmp.ival = _mm_extract_ps(v, 2); break; + case 3: tmp.ival = _mm_extract_ps(v, 3); break; + } + return tmp.fval; +} + +#else + +static SYS_FORCE_INLINE v4si +vm_insert(const v4si v, int32 a, int n) +{ + union { v4si vector; int32 comp[4]; }; + vector = v; + comp[n] = a; + return vector; +} + +static SYS_FORCE_INLINE v4sf +vm_insert(const v4sf v, float a, int n) +{ + union { v4sf vector; float comp[4]; }; + vector = v; + comp[n] = a; + return vector; +} + +static SYS_FORCE_INLINE int +vm_extract(const v4si v, int n) +{ + union { v4si vector; int32 comp[4]; }; + vector = v; + return comp[n]; +} + +static SYS_FORCE_INLINE float +vm_extract(const v4sf v, int n) +{ + union { v4sf vector; float comp[4]; }; + vector = v; + return comp[n]; +} + +#endif + +static SYS_FORCE_INLINE v4sf +vm_splats(float a) +{ + return _mm_set1_ps(a); +} + +static SYS_FORCE_INLINE v4si +vm_splats(uint32 a) +{ + SYS_FPRealUnionF tmp; + tmp.uval = a; + return V4SI(vm_splats(tmp.fval)); +} + +static SYS_FORCE_INLINE v4si +vm_splats(int32 a) +{ + SYS_FPRealUnionF tmp; + tmp.ival = a; + return V4SI(vm_splats(tmp.fval)); +} + +static SYS_FORCE_INLINE v4sf +vm_splats(float a, float b, float c, float d) +{ + return vm_shuffle<0,2,0,2>( + vm_shuffle<0>(_mm_set_ss(a), _mm_set_ss(b)), + vm_shuffle<0>(_mm_set_ss(c), _mm_set_ss(d))); +} + +static SYS_FORCE_INLINE v4si +vm_splats(uint32 a, uint32 b, uint32 c, uint32 d) +{ + SYS_FPRealUnionF af, bf, cf, df; + af.uval = a; + bf.uval = b; + cf.uval = c; + df.uval = d; + return V4SI(vm_splats(af.fval, bf.fval, cf.fval, df.fval)); +} + +static SYS_FORCE_INLINE v4si +vm_splats(int32 a, int32 b, int32 c, int32 d) +{ + SYS_FPRealUnionF af, bf, cf, df; + af.ival = a; + bf.ival = b; + cf.ival = c; + df.ival = d; + return V4SI(vm_splats(af.fval, bf.fval, cf.fval, df.fval)); +} + +static SYS_FORCE_INLINE v4si +vm_load(const int32 v[4]) +{ + return V4SI(_mm_loadu_ps((const float *)v)); +} + +static SYS_FORCE_INLINE v4sf +vm_load(const float v[4]) +{ + return _mm_loadu_ps(v); +} + +static SYS_FORCE_INLINE void +vm_store(float dst[4], v4sf value) +{ + _mm_storeu_ps(dst, value); +} + +static SYS_FORCE_INLINE v4sf +vm_negate(v4sf a) +{ + return _mm_sub_ps(_mm_setzero_ps(), a); +} + +static SYS_FORCE_INLINE v4sf +vm_abs(v4sf a) +{ + return _mm_max_ps(a, vm_negate(a)); +} + +static SYS_FORCE_INLINE v4sf +vm_fdiv(v4sf a, v4sf b) +{ + return _mm_mul_ps(a, _mm_rcp_ps(b)); +} + +static SYS_FORCE_INLINE v4sf +vm_fsqrt(v4sf a) +{ + return _mm_rcp_ps(_mm_rsqrt_ps(a)); +} + +static SYS_FORCE_INLINE v4sf +vm_madd(v4sf a, v4sf b, v4sf c) +{ + return _mm_add_ps(_mm_mul_ps(a, b), c); +} + +static const v4si theSSETrue = vm_splats(0xFFFFFFFF); + +static SYS_FORCE_INLINE bool +vm_allbits(const v4si &a) +{ + return _mm_movemask_ps(V4SF(_mm_cmpeq_epi32(a, theSSETrue))) == 0xF; +} + + +#define VM_EXTRACT vm_extract +#define VM_INSERT vm_insert +#define VM_SPLATS vm_splats +#define VM_LOAD vm_load +#define VM_STORE vm_store + +#define VM_CMPLT(A,B) V4SI(_mm_cmplt_ps(A,B)) +#define VM_CMPLE(A,B) V4SI(_mm_cmple_ps(A,B)) +#define VM_CMPGT(A,B) V4SI(_mm_cmpgt_ps(A,B)) +#define VM_CMPGE(A,B) V4SI(_mm_cmpge_ps(A,B)) +#define VM_CMPEQ(A,B) V4SI(_mm_cmpeq_ps(A,B)) +#define VM_CMPNE(A,B) V4SI(_mm_cmpneq_ps(A,B)) + +#define VM_ICMPLT _mm_cmplt_epi32 +#define VM_ICMPGT _mm_cmpgt_epi32 +#define VM_ICMPEQ _mm_cmpeq_epi32 + +#define VM_IADD _mm_add_epi32 +#define VM_ISUB _mm_sub_epi32 + +#define VM_ADD _mm_add_ps +#define VM_SUB _mm_sub_ps +#define VM_MUL _mm_mul_ps +#define VM_DIV _mm_div_ps +#define VM_SQRT _mm_sqrt_ps +#define VM_ISQRT _mm_rsqrt_ps +#define VM_INVERT _mm_rcp_ps +#define VM_ABS vm_abs + +#define VM_FDIV vm_fdiv +#define VM_NEG vm_negate +#define VM_FSQRT vm_fsqrt +#define VM_MADD vm_madd + +#define VM_MIN _mm_min_ps +#define VM_MAX _mm_max_ps + +#define VM_AND _mm_and_si128 +#define VM_ANDNOT _mm_andnot_si128 +#define VM_OR _mm_or_si128 +#define VM_XOR _mm_xor_si128 + +#define VM_ALLBITS vm_allbits + +#define VM_SHUFFLE vm_shuffle + +// Integer to float conversions +#define VM_SSE_ROUND_MASK 0x6000 +#define VM_SSE_ROUND_ZERO 0x6000 +#define VM_SSE_ROUND_UP 0x4000 +#define VM_SSE_ROUND_DOWN 0x2000 +#define VM_SSE_ROUND_NEAR 0x0000 + +#define GETROUND() (_mm_getcsr()&VM_SSE_ROUND_MASK) +#define SETROUND(x) (_mm_setcsr(x|(_mm_getcsr()&~VM_SSE_ROUND_MASK))) + +// The P functions must be invoked before FLOOR, the E functions invoked +// afterwards to reset the state. + +#define VM_P_FLOOR() uint rounding = GETROUND(); \ + SETROUND(VM_SSE_ROUND_DOWN); +#define VM_FLOOR _mm_cvtps_epi32 +#define VM_INT _mm_cvttps_epi32 +#define VM_E_FLOOR() SETROUND(rounding); + +// Float to integer conversion +#define VM_IFLOAT _mm_cvtepi32_ps +}} + +#endif +#endif +#pragma once +#ifndef __SSE__ +#ifndef __VM_SIMDFunc__ +#define __VM_SIMDFunc__ + + + +#include + +namespace igl { + /// @private + namespace FastWindingNumber { + +struct v4si { + int32 v[4]; +}; + +struct v4sf { + float v[4]; +}; + +static SYS_FORCE_INLINE v4sf V4SF(const v4si &v) { + static_assert(sizeof(v4si) == sizeof(v4sf) && alignof(v4si) == alignof(v4sf), "v4si and v4sf must be compatible"); + return *(const v4sf*)&v; +} + +static SYS_FORCE_INLINE v4si V4SI(const v4sf &v) { + static_assert(sizeof(v4si) == sizeof(v4sf) && alignof(v4si) == alignof(v4sf), "v4si and v4sf must be compatible"); + return *(const v4si*)&v; +} + +static SYS_FORCE_INLINE int32 conditionMask(bool c) { + return c ? int32(0xFFFFFFFF) : 0; +} + +static SYS_FORCE_INLINE v4sf +VM_SPLATS(float f) { + return v4sf{{f, f, f, f}}; +} + +static SYS_FORCE_INLINE v4si +VM_SPLATS(uint32 i) { + return v4si{{int32(i), int32(i), int32(i), int32(i)}}; +} + +static SYS_FORCE_INLINE v4si +VM_SPLATS(int32 i) { + return v4si{{i, i, i, i}}; +} + +static SYS_FORCE_INLINE v4sf +VM_SPLATS(float a, float b, float c, float d) { + return v4sf{{a, b, c, d}}; +} + +static SYS_FORCE_INLINE v4si +VM_SPLATS(uint32 a, uint32 b, uint32 c, uint32 d) { + return v4si{{int32(a), int32(b), int32(c), int32(d)}}; +} + +static SYS_FORCE_INLINE v4si +VM_SPLATS(int32 a, int32 b, int32 c, int32 d) { + return v4si{{a, b, c, d}}; +} + +static SYS_FORCE_INLINE v4si +VM_LOAD(const int32 v[4]) { + return v4si{{v[0], v[1], v[2], v[3]}}; +} + +static SYS_FORCE_INLINE v4sf +VM_LOAD(const float v[4]) { + return v4sf{{v[0], v[1], v[2], v[3]}}; +} + + +static inline v4si VM_ICMPEQ(v4si a, v4si b) { + return v4si{{ + conditionMask(a.v[0] == b.v[0]), + conditionMask(a.v[1] == b.v[1]), + conditionMask(a.v[2] == b.v[2]), + conditionMask(a.v[3] == b.v[3]) + }}; +} + +static inline v4si VM_ICMPGT(v4si a, v4si b) { + return v4si{{ + conditionMask(a.v[0] > b.v[0]), + conditionMask(a.v[1] > b.v[1]), + conditionMask(a.v[2] > b.v[2]), + conditionMask(a.v[3] > b.v[3]) + }}; +} + +static inline v4si VM_ICMPLT(v4si a, v4si b) { + return v4si{{ + conditionMask(a.v[0] < b.v[0]), + conditionMask(a.v[1] < b.v[1]), + conditionMask(a.v[2] < b.v[2]), + conditionMask(a.v[3] < b.v[3]) + }}; +} + +static inline v4si VM_IADD(v4si a, v4si b) { + return v4si{{ + (a.v[0] + b.v[0]), + (a.v[1] + b.v[1]), + (a.v[2] + b.v[2]), + (a.v[3] + b.v[3]) + }}; +} + +static inline v4si VM_ISUB(v4si a, v4si b) { + return v4si{{ + (a.v[0] - b.v[0]), + (a.v[1] - b.v[1]), + (a.v[2] - b.v[2]), + (a.v[3] - b.v[3]) + }}; +} + +static inline v4si VM_OR(v4si a, v4si b) { + return v4si{{ + (a.v[0] | b.v[0]), + (a.v[1] | b.v[1]), + (a.v[2] | b.v[2]), + (a.v[3] | b.v[3]) + }}; +} + +static inline v4si VM_AND(v4si a, v4si b) { + return v4si{{ + (a.v[0] & b.v[0]), + (a.v[1] & b.v[1]), + (a.v[2] & b.v[2]), + (a.v[3] & b.v[3]) + }}; +} + +static inline v4si VM_ANDNOT(v4si a, v4si b) { + return v4si{{ + ((~a.v[0]) & b.v[0]), + ((~a.v[1]) & b.v[1]), + ((~a.v[2]) & b.v[2]), + ((~a.v[3]) & b.v[3]) + }}; +} + +static inline v4si VM_XOR(v4si a, v4si b) { + return v4si{{ + (a.v[0] ^ b.v[0]), + (a.v[1] ^ b.v[1]), + (a.v[2] ^ b.v[2]), + (a.v[3] ^ b.v[3]) + }}; +} + +static SYS_FORCE_INLINE int +VM_EXTRACT(const v4si v, int index) { + return v.v[index]; +} + +static SYS_FORCE_INLINE float +VM_EXTRACT(const v4sf v, int index) { + return v.v[index]; +} + +static SYS_FORCE_INLINE v4si +VM_INSERT(v4si v, int32 value, int index) { + v.v[index] = value; + return v; +} + +static SYS_FORCE_INLINE v4sf +VM_INSERT(v4sf v, float value, int index) { + v.v[index] = value; + return v; +} + +static inline v4si VM_CMPEQ(v4sf a, v4sf b) { + return v4si{{ + conditionMask(a.v[0] == b.v[0]), + conditionMask(a.v[1] == b.v[1]), + conditionMask(a.v[2] == b.v[2]), + conditionMask(a.v[3] == b.v[3]) + }}; +} + +static inline v4si VM_CMPNE(v4sf a, v4sf b) { + return v4si{{ + conditionMask(a.v[0] != b.v[0]), + conditionMask(a.v[1] != b.v[1]), + conditionMask(a.v[2] != b.v[2]), + conditionMask(a.v[3] != b.v[3]) + }}; +} + +static inline v4si VM_CMPGT(v4sf a, v4sf b) { + return v4si{{ + conditionMask(a.v[0] > b.v[0]), + conditionMask(a.v[1] > b.v[1]), + conditionMask(a.v[2] > b.v[2]), + conditionMask(a.v[3] > b.v[3]) + }}; +} + +static inline v4si VM_CMPLT(v4sf a, v4sf b) { + return v4si{{ + conditionMask(a.v[0] < b.v[0]), + conditionMask(a.v[1] < b.v[1]), + conditionMask(a.v[2] < b.v[2]), + conditionMask(a.v[3] < b.v[3]) + }}; +} + +static inline v4si VM_CMPGE(v4sf a, v4sf b) { + return v4si{{ + conditionMask(a.v[0] >= b.v[0]), + conditionMask(a.v[1] >= b.v[1]), + conditionMask(a.v[2] >= b.v[2]), + conditionMask(a.v[3] >= b.v[3]) + }}; +} + +static inline v4si VM_CMPLE(v4sf a, v4sf b) { + return v4si{{ + conditionMask(a.v[0] <= b.v[0]), + conditionMask(a.v[1] <= b.v[1]), + conditionMask(a.v[2] <= b.v[2]), + conditionMask(a.v[3] <= b.v[3]) + }}; +} + +static inline v4sf VM_ADD(v4sf a, v4sf b) { + return v4sf{{ + (a.v[0] + b.v[0]), + (a.v[1] + b.v[1]), + (a.v[2] + b.v[2]), + (a.v[3] + b.v[3]) + }}; +} + +static inline v4sf VM_SUB(v4sf a, v4sf b) { + return v4sf{{ + (a.v[0] - b.v[0]), + (a.v[1] - b.v[1]), + (a.v[2] - b.v[2]), + (a.v[3] - b.v[3]) + }}; +} + +static inline v4sf VM_NEG(v4sf a) { + return v4sf{{ + (-a.v[0]), + (-a.v[1]), + (-a.v[2]), + (-a.v[3]) + }}; +} + +static inline v4sf VM_MUL(v4sf a, v4sf b) { + return v4sf{{ + (a.v[0] * b.v[0]), + (a.v[1] * b.v[1]), + (a.v[2] * b.v[2]), + (a.v[3] * b.v[3]) + }}; +} + +static inline v4sf VM_DIV(v4sf a, v4sf b) { + return v4sf{{ + (a.v[0] / b.v[0]), + (a.v[1] / b.v[1]), + (a.v[2] / b.v[2]), + (a.v[3] / b.v[3]) + }}; +} + +static inline v4sf VM_MADD(v4sf a, v4sf b, v4sf c) { + return v4sf{{ + (a.v[0] * b.v[0]) + c.v[0], + (a.v[1] * b.v[1]) + c.v[1], + (a.v[2] * b.v[2]) + c.v[2], + (a.v[3] * b.v[3]) + c.v[3] + }}; +} + +static inline v4sf VM_ABS(v4sf a) { + return v4sf{{ + (a.v[0] < 0) ? -a.v[0] : a.v[0], + (a.v[1] < 0) ? -a.v[1] : a.v[1], + (a.v[2] < 0) ? -a.v[2] : a.v[2], + (a.v[3] < 0) ? -a.v[3] : a.v[3] + }}; +} + +static inline v4sf VM_MAX(v4sf a, v4sf b) { + return v4sf{{ + (a.v[0] < b.v[0]) ? b.v[0] : a.v[0], + (a.v[1] < b.v[1]) ? b.v[1] : a.v[1], + (a.v[2] < b.v[2]) ? b.v[2] : a.v[2], + (a.v[3] < b.v[3]) ? b.v[3] : a.v[3] + }}; +} + +static inline v4sf VM_MIN(v4sf a, v4sf b) { + return v4sf{{ + (a.v[0] > b.v[0]) ? b.v[0] : a.v[0], + (a.v[1] > b.v[1]) ? b.v[1] : a.v[1], + (a.v[2] > b.v[2]) ? b.v[2] : a.v[2], + (a.v[3] > b.v[3]) ? b.v[3] : a.v[3] + }}; +} + +static inline v4sf VM_INVERT(v4sf a) { + return v4sf{{ + (1.0f/a.v[0]), + (1.0f/a.v[1]), + (1.0f/a.v[2]), + (1.0f/a.v[3]) + }}; +} + +static inline v4sf VM_SQRT(v4sf a) { + return v4sf{{ + std::sqrt(a.v[0]), + std::sqrt(a.v[1]), + std::sqrt(a.v[2]), + std::sqrt(a.v[3]) + }}; +} + +static inline v4si VM_INT(v4sf a) { + return v4si{{ + int32(a.v[0]), + int32(a.v[1]), + int32(a.v[2]), + int32(a.v[3]) + }}; +} + +static inline v4sf VM_IFLOAT(v4si a) { + return v4sf{{ + float(a.v[0]), + float(a.v[1]), + float(a.v[2]), + float(a.v[3]) + }}; +} + +static SYS_FORCE_INLINE void VM_P_FLOOR() {} + +static SYS_FORCE_INLINE int32 singleIntFloor(float f) { + // Casting to int32 usually truncates toward zero, instead of rounding down, + // so subtract one if the result is above f. + int32 i = int32(f); + i -= (float(i) > f); + return i; +} +static inline v4si VM_FLOOR(v4sf a) { + return v4si{{ + singleIntFloor(a.v[0]), + singleIntFloor(a.v[1]), + singleIntFloor(a.v[2]), + singleIntFloor(a.v[3]) + }}; +} + +static SYS_FORCE_INLINE void VM_E_FLOOR() {} + +static SYS_FORCE_INLINE bool vm_allbits(v4si a) { + return ( + (a.v[0] == -1) && + (a.v[1] == -1) && + (a.v[2] == -1) && + (a.v[3] == -1) + ); +} + +int SYS_FORCE_INLINE _mm_movemask_ps(const v4si& v) { + return ( + int(v.v[0] < 0) | + (int(v.v[1] < 0)<<1) | + (int(v.v[2] < 0)<<2) | + (int(v.v[3] < 0)<<3) + ); +} + +int SYS_FORCE_INLINE _mm_movemask_ps(const v4sf& v) { + // Use std::signbit just in case it needs to distinguish between +0 and -0 + // or between positive and negative NaN values (e.g. these could really + // be integers instead of floats). + return ( + int(std::signbit(v.v[0])) | + (int(std::signbit(v.v[1]))<<1) | + (int(std::signbit(v.v[2]))<<2) | + (int(std::signbit(v.v[3]))<<3) + ); +} +}} +#endif +#endif +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * SIMD wrapper classes for 4 floats or 4 ints + */ + +#pragma once + +#ifndef __HDK_VM_SIMD__ +#define __HDK_VM_SIMD__ + + +#include + +//#define FORCE_NON_SIMD + + + + +namespace igl { + /// @private + namespace FastWindingNumber { + +class v4uf; + +class v4uu { +public: + SYS_FORCE_INLINE v4uu() {} + SYS_FORCE_INLINE v4uu(const v4si &v) : vector(v) {} + SYS_FORCE_INLINE v4uu(const v4uu &v) : vector(v.vector) {} + explicit SYS_FORCE_INLINE v4uu(int32 v) { vector = VM_SPLATS(v); } + explicit SYS_FORCE_INLINE v4uu(const int32 v[4]) + { vector = VM_LOAD(v); } + SYS_FORCE_INLINE v4uu(int32 a, int32 b, int32 c, int32 d) + { vector = VM_SPLATS(a, b, c, d); } + + // Assignment + SYS_FORCE_INLINE v4uu operator=(int32 v) + { vector = v4uu(v).vector; return *this; } + SYS_FORCE_INLINE v4uu operator=(v4si v) + { vector = v; return *this; } + SYS_FORCE_INLINE v4uu operator=(const v4uu &v) + { vector = v.vector; return *this; } + + SYS_FORCE_INLINE void condAssign(const v4uu &val, const v4uu &c) + { *this = (c & val) | ((!c) & *this); } + + // Comparison + SYS_FORCE_INLINE v4uu operator == (const v4uu &v) const + { return v4uu(VM_ICMPEQ(vector, v.vector)); } + SYS_FORCE_INLINE v4uu operator != (const v4uu &v) const + { return ~(*this == v); } + SYS_FORCE_INLINE v4uu operator > (const v4uu &v) const + { return v4uu(VM_ICMPGT(vector, v.vector)); } + SYS_FORCE_INLINE v4uu operator < (const v4uu &v) const + { return v4uu(VM_ICMPLT(vector, v.vector)); } + SYS_FORCE_INLINE v4uu operator >= (const v4uu &v) const + { return ~(*this < v); } + SYS_FORCE_INLINE v4uu operator <= (const v4uu &v) const + { return ~(*this > v); } + + SYS_FORCE_INLINE v4uu operator == (int32 v) const { return *this == v4uu(v); } + SYS_FORCE_INLINE v4uu operator != (int32 v) const { return *this != v4uu(v); } + SYS_FORCE_INLINE v4uu operator > (int32 v) const { return *this > v4uu(v); } + SYS_FORCE_INLINE v4uu operator < (int32 v) const { return *this < v4uu(v); } + SYS_FORCE_INLINE v4uu operator >= (int32 v) const { return *this >= v4uu(v); } + SYS_FORCE_INLINE v4uu operator <= (int32 v) const { return *this <= v4uu(v); } + + // Basic math + SYS_FORCE_INLINE v4uu operator+(const v4uu &r) const + { return v4uu(VM_IADD(vector, r.vector)); } + SYS_FORCE_INLINE v4uu operator-(const v4uu &r) const + { return v4uu(VM_ISUB(vector, r.vector)); } + SYS_FORCE_INLINE v4uu operator+=(const v4uu &r) { return (*this = *this + r); } + SYS_FORCE_INLINE v4uu operator-=(const v4uu &r) { return (*this = *this - r); } + SYS_FORCE_INLINE v4uu operator+(int32 r) const { return *this + v4uu(r); } + SYS_FORCE_INLINE v4uu operator-(int32 r) const { return *this - v4uu(r); } + SYS_FORCE_INLINE v4uu operator+=(int32 r) { return (*this = *this + r); } + SYS_FORCE_INLINE v4uu operator-=(int32 r) { return (*this = *this - r); } + + // logical/bitwise + + SYS_FORCE_INLINE v4uu operator||(const v4uu &r) const + { return v4uu(VM_OR(vector, r.vector)); } + SYS_FORCE_INLINE v4uu operator&&(const v4uu &r) const + { return v4uu(VM_AND(vector, r.vector)); } + SYS_FORCE_INLINE v4uu operator^(const v4uu &r) const + { return v4uu(VM_XOR(vector, r.vector)); } + SYS_FORCE_INLINE v4uu operator!() const + { return *this == v4uu(0); } + + SYS_FORCE_INLINE v4uu operator|(const v4uu &r) const { return *this || r; } + SYS_FORCE_INLINE v4uu operator&(const v4uu &r) const { return *this && r; } + SYS_FORCE_INLINE v4uu operator~() const + { return *this ^ v4uu(0xFFFFFFFF); } + + // component + SYS_FORCE_INLINE int32 operator[](int idx) const { return VM_EXTRACT(vector, idx); } + SYS_FORCE_INLINE void setComp(int idx, int32 v) { vector = VM_INSERT(vector, v, idx); } + + v4uf toFloat() const; + +public: + v4si vector; +}; + +class v4uf { +public: + SYS_FORCE_INLINE v4uf() {} + SYS_FORCE_INLINE v4uf(const v4sf &v) : vector(v) {} + SYS_FORCE_INLINE v4uf(const v4uf &v) : vector(v.vector) {} + explicit SYS_FORCE_INLINE v4uf(float v) { vector = VM_SPLATS(v); } + explicit SYS_FORCE_INLINE v4uf(const float v[4]) + { vector = VM_LOAD(v); } + SYS_FORCE_INLINE v4uf(float a, float b, float c, float d) + { vector = VM_SPLATS(a, b, c, d); } + + // Assignment + SYS_FORCE_INLINE v4uf operator=(float v) + { vector = v4uf(v).vector; return *this; } + SYS_FORCE_INLINE v4uf operator=(v4sf v) + { vector = v; return *this; } + SYS_FORCE_INLINE v4uf operator=(const v4uf &v) + { vector = v.vector; return *this; } + + SYS_FORCE_INLINE void condAssign(const v4uf &val, const v4uu &c) + { *this = (val & c) | (*this & ~c); } + + // Comparison + SYS_FORCE_INLINE v4uu operator == (const v4uf &v) const + { return v4uu(VM_CMPEQ(vector, v.vector)); } + SYS_FORCE_INLINE v4uu operator != (const v4uf &v) const + { return v4uu(VM_CMPNE(vector, v.vector)); } + SYS_FORCE_INLINE v4uu operator > (const v4uf &v) const + { return v4uu(VM_CMPGT(vector, v.vector)); } + SYS_FORCE_INLINE v4uu operator < (const v4uf &v) const + { return v4uu(VM_CMPLT(vector, v.vector)); } + SYS_FORCE_INLINE v4uu operator >= (const v4uf &v) const + { return v4uu(VM_CMPGE(vector, v.vector)); } + SYS_FORCE_INLINE v4uu operator <= (const v4uf &v) const + { return v4uu(VM_CMPLE(vector, v.vector)); } + + SYS_FORCE_INLINE v4uu operator == (float v) const { return *this == v4uf(v); } + SYS_FORCE_INLINE v4uu operator != (float v) const { return *this != v4uf(v); } + SYS_FORCE_INLINE v4uu operator > (float v) const { return *this > v4uf(v); } + SYS_FORCE_INLINE v4uu operator < (float v) const { return *this < v4uf(v); } + SYS_FORCE_INLINE v4uu operator >= (float v) const { return *this >= v4uf(v); } + SYS_FORCE_INLINE v4uu operator <= (float v) const { return *this <= v4uf(v); } + + + // Basic math + SYS_FORCE_INLINE v4uf operator+(const v4uf &r) const + { return v4uf(VM_ADD(vector, r.vector)); } + SYS_FORCE_INLINE v4uf operator-(const v4uf &r) const + { return v4uf(VM_SUB(vector, r.vector)); } + SYS_FORCE_INLINE v4uf operator-() const + { return v4uf(VM_NEG(vector)); } + SYS_FORCE_INLINE v4uf operator*(const v4uf &r) const + { return v4uf(VM_MUL(vector, r.vector)); } + SYS_FORCE_INLINE v4uf operator/(const v4uf &r) const + { return v4uf(VM_DIV(vector, r.vector)); } + + SYS_FORCE_INLINE v4uf operator+=(const v4uf &r) { return (*this = *this + r); } + SYS_FORCE_INLINE v4uf operator-=(const v4uf &r) { return (*this = *this - r); } + SYS_FORCE_INLINE v4uf operator*=(const v4uf &r) { return (*this = *this * r); } + SYS_FORCE_INLINE v4uf operator/=(const v4uf &r) { return (*this = *this / r); } + + SYS_FORCE_INLINE v4uf operator+(float r) const { return *this + v4uf(r); } + SYS_FORCE_INLINE v4uf operator-(float r) const { return *this - v4uf(r); } + SYS_FORCE_INLINE v4uf operator*(float r) const { return *this * v4uf(r); } + SYS_FORCE_INLINE v4uf operator/(float r) const { return *this / v4uf(r); } + SYS_FORCE_INLINE v4uf operator+=(float r) { return (*this = *this + r); } + SYS_FORCE_INLINE v4uf operator-=(float r) { return (*this = *this - r); } + SYS_FORCE_INLINE v4uf operator*=(float r) { return (*this = *this * r); } + SYS_FORCE_INLINE v4uf operator/=(float r) { return (*this = *this / r); } + + // logical/bitwise + + SYS_FORCE_INLINE v4uf operator||(const v4uu &r) const + { return v4uf(V4SF(VM_OR(V4SI(vector), r.vector))); } + SYS_FORCE_INLINE v4uf operator&&(const v4uu &r) const + { return v4uf(V4SF(VM_AND(V4SI(vector), r.vector))); } + SYS_FORCE_INLINE v4uf operator^(const v4uu &r) const + { return v4uf(V4SF(VM_XOR(V4SI(vector), r.vector))); } + SYS_FORCE_INLINE v4uf operator!() const + { return v4uf(V4SF((*this == v4uf(0.0F)).vector)); } + + SYS_FORCE_INLINE v4uf operator||(const v4uf &r) const + { return v4uf(V4SF(VM_OR(V4SI(vector), V4SI(r.vector)))); } + SYS_FORCE_INLINE v4uf operator&&(const v4uf &r) const + { return v4uf(V4SF(VM_AND(V4SI(vector), V4SI(r.vector)))); } + SYS_FORCE_INLINE v4uf operator^(const v4uf &r) const + { return v4uf(V4SF(VM_XOR(V4SI(vector), V4SI(r.vector)))); } + + SYS_FORCE_INLINE v4uf operator|(const v4uu &r) const { return *this || r; } + SYS_FORCE_INLINE v4uf operator&(const v4uu &r) const { return *this && r; } + SYS_FORCE_INLINE v4uf operator~() const + { return *this ^ v4uu(0xFFFFFFFF); } + + SYS_FORCE_INLINE v4uf operator|(const v4uf &r) const { return *this || r; } + SYS_FORCE_INLINE v4uf operator&(const v4uf &r) const { return *this && r; } + + // component + SYS_FORCE_INLINE float operator[](int idx) const { return VM_EXTRACT(vector, idx); } + SYS_FORCE_INLINE void setComp(int idx, float v) { vector = VM_INSERT(vector, v, idx); } + + // more math + SYS_FORCE_INLINE v4uf abs() const { return v4uf(VM_ABS(vector)); } + SYS_FORCE_INLINE v4uf clamp(const v4uf &low, const v4uf &high) const + { return v4uf( + VM_MIN(VM_MAX(vector, low.vector), high.vector)); } + SYS_FORCE_INLINE v4uf clamp(float low, float high) const + { return v4uf(VM_MIN(VM_MAX(vector, + v4uf(low).vector), v4uf(high).vector)); } + SYS_FORCE_INLINE v4uf recip() const { return v4uf(VM_INVERT(vector)); } + + /// This is a lie, it is a signed int. + SYS_FORCE_INLINE v4uu toUnsignedInt() const { return VM_INT(vector); } + SYS_FORCE_INLINE v4uu toSignedInt() const { return VM_INT(vector); } + + v4uu floor() const + { + VM_P_FLOOR(); + v4uu result = VM_FLOOR(vector); + VM_E_FLOOR(); + return result; + } + + /// Returns the integer part of this float, this becomes the + /// 0..1 fractional component. + v4uu splitFloat() + { + v4uu base = toSignedInt(); + *this -= base.toFloat(); + return base; + } + +#ifdef __SSE__ + template + SYS_FORCE_INLINE v4uf swizzle() const + { + return VM_SHUFFLE(vector); + } +#endif + + SYS_FORCE_INLINE v4uu isFinite() const + { + // If the exponent is the maximum value, it's either infinite or NaN. + const v4si mask = VM_SPLATS(0x7F800000); + return ~v4uu(VM_ICMPEQ(VM_AND(V4SI(vector), mask), mask)); + } + +public: + v4sf vector; +}; + +SYS_FORCE_INLINE v4uf +v4uu::toFloat() const +{ + return v4uf(VM_IFLOAT(vector)); +} + +// +// Custom vector operations +// + +static SYS_FORCE_INLINE v4uf +sqrt(const v4uf &a) +{ + return v4uf(VM_SQRT(a.vector)); +} + +static SYS_FORCE_INLINE v4uf +fabs(const v4uf &a) +{ + return a.abs(); +} + +// Use this operation to mask disabled values to 0 +// rval = !a ? b : 0; + +static SYS_FORCE_INLINE v4uf +andn(const v4uu &a, const v4uf &b) +{ + return v4uf(V4SF(VM_ANDNOT(a.vector, V4SI(b.vector)))); +} + +static SYS_FORCE_INLINE v4uu +andn(const v4uu &a, const v4uu &b) +{ + return v4uu(VM_ANDNOT(a.vector, b.vector)); +} + +// rval = a ? b : c; +static SYS_FORCE_INLINE v4uf +ternary(const v4uu &a, const v4uf &b, const v4uf &c) +{ + return (b & a) | andn(a, c); +} + +static SYS_FORCE_INLINE v4uu +ternary(const v4uu &a, const v4uu &b, const v4uu &c) +{ + return (b & a) | andn(a, c); +} + +// rval = !(a && b) +static SYS_FORCE_INLINE v4uu +nand(const v4uu &a, const v4uu &b) +{ + return !v4uu(VM_AND(a.vector, b.vector)); +} + +static SYS_FORCE_INLINE v4uf +vmin(const v4uf &a, const v4uf &b) +{ + return v4uf(VM_MIN(a.vector, b.vector)); +} + +static SYS_FORCE_INLINE v4uf +vmax(const v4uf &a, const v4uf &b) +{ + return v4uf(VM_MAX(a.vector, b.vector)); +} + +static SYS_FORCE_INLINE v4uf +clamp(const v4uf &a, const v4uf &b, const v4uf &c) +{ + return vmax(vmin(a, c), b); +} + +static SYS_FORCE_INLINE v4uf +clamp(const v4uf &a, float b, float c) +{ + return vmax(vmin(a, v4uf(c)), v4uf(b)); +} + +static SYS_FORCE_INLINE bool +allbits(const v4uu &a) +{ + return vm_allbits(a.vector); +} + +static SYS_FORCE_INLINE bool +anybits(const v4uu &a) +{ + return !allbits(~a); +} + +static SYS_FORCE_INLINE v4uf +madd(const v4uf &v, const v4uf &f, const v4uf &a) +{ + return v4uf(VM_MADD(v.vector, f.vector, a.vector)); +} + +static SYS_FORCE_INLINE v4uf +madd(const v4uf &v, float f, float a) +{ + return v4uf(VM_MADD(v.vector, v4uf(f).vector, v4uf(a).vector)); +} + +static SYS_FORCE_INLINE v4uf +madd(const v4uf &v, float f, const v4uf &a) +{ + return v4uf(VM_MADD(v.vector, v4uf(f).vector, a.vector)); +} + +static SYS_FORCE_INLINE v4uf +msub(const v4uf &v, const v4uf &f, const v4uf &s) +{ + return madd(v, f, -s); +} + +static SYS_FORCE_INLINE v4uf +msub(const v4uf &v, float f, float s) +{ + return madd(v, f, -s); +} + +static SYS_FORCE_INLINE v4uf +lerp(const v4uf &a, const v4uf &b, const v4uf &w) +{ + v4uf w1 = v4uf(1.0F) - w; + return madd(a, w1, b*w); +} + +static SYS_FORCE_INLINE v4uf +luminance(const v4uf &r, const v4uf &g, const v4uf &b, + float rw, float gw, float bw) +{ + return v4uf(madd(r, v4uf(rw), madd(g, v4uf(gw), b * bw))); +} + +static SYS_FORCE_INLINE float +dot3(const v4uf &a, const v4uf &b) +{ + v4uf res = a*b; + return res[0] + res[1] + res[2]; +} + +static SYS_FORCE_INLINE float +dot4(const v4uf &a, const v4uf &b) +{ + v4uf res = a*b; + return res[0] + res[1] + res[2] + res[3]; +} + +static SYS_FORCE_INLINE float +length(const v4uf &a) +{ + return SYSsqrt(dot3(a, a)); +} + +static SYS_FORCE_INLINE v4uf +normalize(const v4uf &a) +{ + return a / length(a); +} + +static SYS_FORCE_INLINE v4uf +cross(const v4uf &a, const v4uf &b) +{ + return v4uf(a[1]*b[2] - a[2]*b[1], + a[2]*b[0] - a[0]*b[2], + a[0]*b[1] - a[1]*b[0], 0); +} + +// Currently there is no specific support for signed integers +typedef v4uu v4ui; + +// Assuming that ptr is an array of elements of type STYPE, this operation +// will return the index of the first element that is aligned to (1< +#include +#include +#include + +namespace igl { + /// @private + namespace FastWindingNumber { + + /// This routine describes how to change the size of an array. + /// It must increase the current_size by at least one! + /// + /// Current expected sequence of small sizes: + /// 4, 8, 16, 32, 48, 64, 80, 96, 112, + /// 128, 256, 384, 512, 640, 768, 896, 1024, + /// (increases by approx factor of 1.125 each time after this) +template +static inline T +UTbumpAlloc(T current_size) +{ + // NOTE: These must be powers of two. See below. + constexpr T SMALL_ALLOC(16); + constexpr T BIG_ALLOC(128); + + // For small values, we increment by fixed amounts. For + // large values, we increment by one eighth of the current size. + // This prevents n^2 behaviour with allocation one element at a time. + // A factor of 1/8 will waste 1/16 the memory on average, and will + // double the size of the array in approximately 6 reallocations. + if (current_size < T(8)) + { + return (current_size < T(4)) ? T(4) : T(8); + } + if (current_size < T(BIG_ALLOC)) + { + // Snap up to next multiple of SMALL_ALLOC (must be power of 2) + return (current_size + T(SMALL_ALLOC)) & ~T(SMALL_ALLOC-1); + } + if (current_size < T(BIG_ALLOC * 8)) + { + // Snap up to next multiple of BIG_ALLOC (must be power of 2) + return (current_size + T(BIG_ALLOC)) & ~T(BIG_ALLOC-1); + } + + T bump = current_size >> 3; // Divided by 8. + current_size += bump; + return current_size; +} + +template +class UT_Array +{ +public: + typedef T value_type; + + typedef int (*Comparator)(const T *, const T *); + + /// Copy constructor. It duplicates the data. + /// It's marked explicit so that it's not accidentally passed by value. + /// You can always pass by reference and then copy it, if needed. + /// If you have a line like: + /// UT_Array a = otherarray; + /// and it really does need to copy instead of referencing, + /// you can rewrite it as: + /// UT_Array a(otherarray); + inline explicit UT_Array(const UT_Array &a); + + /// Move constructor. Steals the working data from the original. + inline UT_Array(UT_Array &&a) noexcept; + + /// Construct based on given capacity and size + UT_Array(exint capacity, exint size) + { + myData = capacity ? allocateCapacity(capacity) : NULL; + if (capacity < size) + size = capacity; + mySize = size; + myCapacity = capacity; + trivialConstructRange(myData, mySize); + } + + /// Construct based on given capacity with a size of 0 + explicit UT_Array(exint capacity = 0) : myCapacity(capacity), mySize(0) + { + myData = capacity ? allocateCapacity(capacity) : NULL; + } + + /// Construct with the contents of an initializer list + inline explicit UT_Array(std::initializer_list init); + + inline ~UT_Array(); + + inline void swap(UT_Array &other); + + /// Append an element to the current elements and return its index in the + /// array, or insert the element at a specified position; if necessary, + /// insert() grows the array to accommodate the element. The insert + /// methods use the assignment operator '=' to place the element into the + /// right spot; be aware that '=' works differently on objects and pointers. + /// The test for duplicates uses the logical equal operator '=='; as with + /// '=', the behaviour of the equality operator on pointers versus objects + /// is not the same. + /// Use the subscript operators instead of insert() if you are appending + /// to the array, or if you don't mind overwriting the element already + /// inserted at the given index. + exint append(void) { return insert(mySize); } + exint append(const T &t) { return appendImpl(t); } + exint append(T &&t) { return appendImpl(std::move(t)); } + inline void append(const T *pt, exint count); + inline void appendMultiple(const T &t, exint count); + inline exint insert(exint index); + exint insert(const T &t, exint i) + { return insertImpl(t, i); } + exint insert(T &&t, exint i) + { return insertImpl(std::move(t), i); } + + /// Adds a new element to the array (resizing if necessary) and forwards + /// the given arguments to T's constructor. + /// NOTE: Unlike append(), the arguments cannot reference any existing + /// elements in the array. Checking for and handling such cases would + /// remove most of the performance gain versus append(T(...)). Debug builds + /// will assert that the arguments are valid. + template + inline exint emplace_back(S&&... s); + + /// Takes another T array and concatenate it onto my end + inline exint concat(const UT_Array &a); + + /// Insert an element "count" times at the given index. Return the index. + inline exint multipleInsert(exint index, exint count); + + /// An alias for unique element insertion at a certain index. Also used by + /// the other insertion methods. + exint insertAt(const T &t, exint index) + { return insertImpl(t, index); } + + /// Return true if given index is valid. + bool isValidIndex(exint index) const + { return (index >= 0 && index < mySize); } + + /// Remove one element from the array given its + /// position in the list, and fill the gap by shifting the elements down + /// by one position. Return the index of the element removed or -1 if + /// the index was out of bounds. + exint removeIndex(exint index) + { + return isValidIndex(index) ? removeAt(index) : -1; + } + void removeLast() + { + if (mySize) removeAt(mySize-1); + } + + /// Remove the range [begin_i,end_i) of elements from the array. + inline void removeRange(exint begin_i, exint end_i); + + /// Remove the range [begin_i, end_i) of elements from this array and place + /// them in the dest array, shrinking/growing the dest array as necessary. + inline void extractRange(exint begin_i, exint end_i, + UT_Array& dest); + + /// Removes all matching elements from the list, shuffling down and changing + /// the size appropriately. + /// Returns the number of elements left. + template + inline exint removeIf(IsEqual is_equal); + + /// Remove all matching elements. Also sets the capacity of the array. + template + void collapseIf(IsEqual is_equal) + { + removeIf(is_equal); + setCapacity(size()); + } + + /// Move howMany objects starting at index srcIndex to destIndex; + /// This method will remove the elements at [srcIdx, srcIdx+howMany) and + /// then insert them at destIdx. This method can be used in place of + /// the old shift() operation. + inline void move(exint srcIdx, exint destIdx, exint howMany); + + /// Cyclically shifts the entire array by howMany + inline void cycle(exint howMany); + + /// Quickly set the array to a single value. + inline void constant(const T &v); + /// Zeros the array if a POD type, else trivial constructs if a class type. + inline void zero(); + + /// The fastest search possible, which does pointer arithmetic to find the + /// index of the element. WARNING: index() does no out-of-bounds checking. + exint index(const T &t) const { return &t - myData; } + exint safeIndex(const T &t) const + { + return (&t >= myData && &t < (myData + mySize)) + ? &t - myData : -1; + } + + /// Set the capacity of the array, i.e. grow it or shrink it. The + /// function copies the data after reallocating space for the array. + inline void setCapacity(exint newcapacity); + void setCapacityIfNeeded(exint mincapacity) + { + if (capacity() < mincapacity) + setCapacity(mincapacity); + } + /// If the capacity is smaller than mincapacity, expand the array + /// to at least mincapacity and to at least a constant factor of the + /// array's previous capacity, to avoid having a linear number of + /// reallocations in a linear number of calls to bumpCapacity. + void bumpCapacity(exint mincapacity) + { + if (capacity() >= mincapacity) + return; + // The following 4 lines are just + // SYSmax(mincapacity, UTbumpAlloc(capacity())), avoiding SYSmax + exint bumped = UTbumpAlloc(capacity()); + exint newcapacity = mincapacity; + if (bumped > mincapacity) + newcapacity = bumped; + setCapacity(newcapacity); + } + + /// First bumpCapacity to ensure that there's space for newsize, + /// expanding either not at all or by at least a constant factor + /// of the array's previous capacity, + /// then set the size to newsize. + void bumpSize(exint newsize) + { + bumpCapacity(newsize); + setSize(newsize); + } + /// NOTE: bumpEntries() will be deprecated in favour of bumpSize() in a + /// future version. + void bumpEntries(exint newsize) + { + bumpSize(newsize); + } + + /// Query the capacity, i.e. the allocated length of the array. + /// NOTE: capacity() >= size(). + exint capacity() const { return myCapacity; } + /// Query the size, i.e. the number of occupied elements in the array. + /// NOTE: capacity() >= size(). + exint size() const { return mySize; } + /// Alias of size(). size() is preferred. + exint entries() const { return mySize; } + /// Returns true iff there are no occupied elements in the array. + bool isEmpty() const { return mySize==0; } + + /// Set the size, the number of occupied elements in the array. + /// NOTE: This will not do bumpCapacity, so if you call this + /// n times to increase the size, it may take + /// n^2 time. + void setSize(exint newsize) + { + if (newsize < 0) + newsize = 0; + if (newsize == mySize) + return; + setCapacityIfNeeded(newsize); + if (mySize > newsize) + trivialDestructRange(myData + newsize, mySize - newsize); + else // newsize > mySize + trivialConstructRange(myData + mySize, newsize - mySize); + mySize = newsize; + } + /// Alias of setSize(). setSize() is preferred. + void entries(exint newsize) + { + setSize(newsize); + } + /// Set the size, but unlike setSize(newsize), this function + /// will not initialize new POD elements to zero. Non-POD data types + /// will still have their constructors called. + /// This function is faster than setSize(ne) if you intend to fill in + /// data for all elements. + void setSizeNoInit(exint newsize) + { + if (newsize < 0) + newsize = 0; + if (newsize == mySize) + return; + setCapacityIfNeeded(newsize); + if (mySize > newsize) + trivialDestructRange(myData + newsize, mySize - newsize); + else if (!isPOD()) // newsize > mySize + trivialConstructRange(myData + mySize, newsize - mySize); + mySize = newsize; + } + + /// Decreases, but never expands, to the given maxsize. + void truncate(exint maxsize) + { + if (maxsize >= 0 && size() > maxsize) + setSize(maxsize); + } + /// Resets list to an empty list. + void clear() { + // Don't call setSize(0) since that would require a valid default + // constructor. + trivialDestructRange(myData, mySize); + mySize = 0; + } + + /// Assign array a to this array by copying each of a's elements with + /// memcpy for POD types, and with copy construction for class types. + inline UT_Array & operator=(const UT_Array &a); + + /// Replace the contents with those from the initializer_list ilist + inline UT_Array & operator=(std::initializer_list ilist); + + /// Move the contents of array a to this array. + inline UT_Array & operator=(UT_Array &&a); + + /// Compare two array and return true if they are equal and false otherwise. + /// Two elements are checked against each other using operator '==' or + /// compare() respectively. + /// NOTE: The capacities of the arrays are not checked when + /// determining whether they are equal. + inline bool operator==(const UT_Array &a) const; + inline bool operator!=(const UT_Array &a) const; + + /// Subscript operator + /// NOTE: This does NOT do any bounds checking unless paranoid + /// asserts are enabled. + T & operator()(exint i) + { + UT_IGL_ASSERT_P(i >= 0 && i < mySize); + return myData[i]; + } + /// Const subscript operator + /// NOTE: This does NOT do any bounds checking unless paranoid + /// asserts are enabled. + const T & operator()(exint i) const + { + UT_IGL_ASSERT_P(i >= 0 && i < mySize); + return myData[i]; + } + + /// Subscript operator + /// NOTE: This does NOT do any bounds checking unless paranoid + /// asserts are enabled. + T & operator[](exint i) + { + UT_IGL_ASSERT_P(i >= 0 && i < mySize); + return myData[i]; + } + /// Const subscript operator + /// NOTE: This does NOT do any bounds checking unless paranoid + /// asserts are enabled. + const T & operator[](exint i) const + { + UT_IGL_ASSERT_P(i >= 0 && i < mySize); + return myData[i]; + } + + /// forcedRef(exint) will grow the array if necessary, initializing any + /// new elements to zero for POD types and default constructing for + /// class types. + T & forcedRef(exint i) + { + UT_IGL_ASSERT_P(i >= 0); + if (i >= mySize) + bumpSize(i+1); + return myData[i]; + } + + /// forcedGet(exint) does NOT grow the array, and will return default + /// objects for out of bound array indices. + T forcedGet(exint i) const + { + return (i >= 0 && i < mySize) ? myData[i] : T(); + } + + T & last() + { + UT_IGL_ASSERT_P(mySize); + return myData[mySize-1]; + } + const T & last() const + { + UT_IGL_ASSERT_P(mySize); + return myData[mySize-1]; + } + + T * getArray() const { return myData; } + const T * getRawArray() const { return myData; } + + T * array() { return myData; } + const T * array() const { return myData; } + + T * data() { return myData; } + const T * data() const { return myData; } + + /// This method allows you to swap in a new raw T array, which must be + /// the same size as myCapacity. Use caution with this method. + T * aliasArray(T *newdata) + { T *data = myData; myData = newdata; return data; } + + template + class base_iterator + { + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = IT; + using difference_type = exint; + using pointer = value_type *; + using reference = value_type &; + + // Note: When we drop gcc 4.4 support and allow range-based for + // loops, we should also drop atEnd(), which means we can drop + // myEnd here. + base_iterator() : myCurrent(NULL), myEnd(NULL) {} + + // Allow iterator to const_iterator conversion + template + base_iterator(const base_iterator &src) + : myCurrent(src.myCurrent), myEnd(src.myEnd) {} + + pointer operator->() const + { return FORWARD ? myCurrent : myCurrent - 1; } + + reference operator*() const + { return FORWARD ? *myCurrent : myCurrent[-1]; } + + reference item() const + { return FORWARD ? *myCurrent : myCurrent[-1]; } + + reference operator[](exint n) const + { return FORWARD ? myCurrent[n] : myCurrent[-n - 1]; } + + /// Pre-increment operator + base_iterator &operator++() + { + if (FORWARD) ++myCurrent; else --myCurrent; + return *this; + } + /// Post-increment operator + base_iterator operator++(int) + { + base_iterator tmp = *this; + if (FORWARD) ++myCurrent; else --myCurrent; + return tmp; + } + /// Pre-decrement operator + base_iterator &operator--() + { + if (FORWARD) --myCurrent; else ++myCurrent; + return *this; + } + /// Post-decrement operator + base_iterator operator--(int) + { + base_iterator tmp = *this; + if (FORWARD) --myCurrent; else ++myCurrent; + return tmp; + } + + base_iterator &operator+=(exint n) + { + if (FORWARD) + myCurrent += n; + else + myCurrent -= n; + return *this; + } + base_iterator operator+(exint n) const + { + if (FORWARD) + return base_iterator(myCurrent + n, myEnd); + else + return base_iterator(myCurrent - n, myEnd); + } + + base_iterator &operator-=(exint n) + { return (*this) += (-n); } + base_iterator operator-(exint n) const + { return (*this) + (-n); } + + bool atEnd() const { return myCurrent == myEnd; } + void advance() { this->operator++(); } + + // Comparators + template + bool operator==(const base_iterator &r) const + { return myCurrent == r.myCurrent; } + + template + bool operator!=(const base_iterator &r) const + { return myCurrent != r.myCurrent; } + + template + bool operator<(const base_iterator &r) const + { + if (FORWARD) + return myCurrent < r.myCurrent; + else + return r.myCurrent < myCurrent; + } + + template + bool operator>(const base_iterator &r) const + { + if (FORWARD) + return myCurrent > r.myCurrent; + else + return r.myCurrent > myCurrent; + } + + template + bool operator<=(const base_iterator &r) const + { + if (FORWARD) + return myCurrent <= r.myCurrent; + else + return r.myCurrent <= myCurrent; + } + + template + bool operator>=(const base_iterator &r) const + { + if (FORWARD) + return myCurrent >= r.myCurrent; + else + return r.myCurrent >= myCurrent; + } + + // Difference operator for std::distance + template + exint operator-(const base_iterator &r) const + { + if (FORWARD) + return exint(myCurrent - r.myCurrent); + else + return exint(r.myCurrent - myCurrent); + } + + + protected: + friend class UT_Array; + base_iterator(IT *c, IT *e) : myCurrent(c), myEnd(e) {} + private: + + IT *myCurrent; + IT *myEnd; + }; + + typedef base_iterator iterator; + typedef base_iterator const_iterator; + typedef base_iterator reverse_iterator; + typedef base_iterator const_reverse_iterator; + typedef const_iterator traverser; // For backward compatibility + + /// Begin iterating over the array. The contents of the array may be + /// modified during the traversal. + iterator begin() + { + return iterator(myData, myData + mySize); + } + /// End iterator. + iterator end() + { + return iterator(myData + mySize, + myData + mySize); + } + + /// Begin iterating over the array. The array may not be modified during + /// the traversal. + const_iterator begin() const + { + return const_iterator(myData, myData + mySize); + } + /// End const iterator. Consider using it.atEnd() instead. + const_iterator end() const + { + return const_iterator(myData + mySize, + myData + mySize); + } + + /// Begin iterating over the array in reverse. + reverse_iterator rbegin() + { + return reverse_iterator(myData + mySize, + myData); + } + /// End reverse iterator. + reverse_iterator rend() + { + return reverse_iterator(myData, myData); + } + /// Begin iterating over the array in reverse. + const_reverse_iterator rbegin() const + { + return const_reverse_iterator(myData + mySize, + myData); + } + /// End reverse iterator. Consider using it.atEnd() instead. + const_reverse_iterator rend() const + { + return const_reverse_iterator(myData, myData); + } + + /// Remove item specified by the reverse_iterator. + void removeItem(const reverse_iterator &it) + { + removeAt(&it.item() - myData); + } + + + /// Very dangerous methods to share arrays. + /// The array is not aware of the sharing, so ensure you clear + /// out the array prior a destructor or setCapacity operation. + void unsafeShareData(UT_Array &src) + { + myData = src.myData; + myCapacity = src.myCapacity; + mySize = src.mySize; + } + void unsafeShareData(T *src, exint srcsize) + { + myData = src; + myCapacity = srcsize; + mySize = srcsize; + } + void unsafeShareData(T *src, exint size, exint capacity) + { + myData = src; + mySize = size; + myCapacity = capacity; + } + void unsafeClearData() + { + myData = NULL; + myCapacity = 0; + mySize = 0; + } + + /// Returns true if the data used by the array was allocated on the heap. + inline bool isHeapBuffer() const + { + return (myData != (T *)(((char*)this) + sizeof(*this))); + } + inline bool isHeapBuffer(T* data) const + { + return (data != (T *)(((char*)this) + sizeof(*this))); + } + +protected: + // Check whether T may have a constructor, destructor, or copy + // constructor. This test is conservative in that some POD types will + // not be recognized as POD by this function. To mark your type as POD, + // use the SYS_DECLARE_IS_POD() macro in SYS_TypeDecorate.h. + static constexpr SYS_FORCE_INLINE bool isPOD() + { + return std::is_standard_layout::value && + std::is_trivially_default_constructible::value && + std::is_trivially_copyable::value && + std::is_trivially_move_assignable::value && + std::is_trivially_destructible::value; + } + + /// Implements both append(const T &) and append(T &&) via perfect + /// forwarding. Unlike the variadic emplace_back(), its argument may be a + /// reference to another element in the array. + template + inline exint appendImpl(S &&s); + + /// Similar to appendImpl() but for insertion. + template + inline exint insertImpl(S &&s, exint index); + + // Construct the given type + template + static void construct(T &dst, S&&... s) + { + new (&dst) T(std::forward(s)...); + } + + // Copy construct the given type + static void copyConstruct(T &dst, const T &src) + { + if (isPOD()) + dst = src; + else + new (&dst) T(src); + } + static void copyConstructRange(T *dst, const T *src, exint n) + { + if (isPOD()) + { + if (n > 0) + { + ::memcpy((void *)dst, (const void *)src, + n * sizeof(T)); + } + } + else + { + for (exint i = 0; i < n; i++) + new (&dst[i]) T(src[i]); + } + } + + /// Element Constructor + static void trivialConstruct(T &dst) + { + if (!isPOD()) + new (&dst) T(); + else + memset((void *)&dst, 0, sizeof(T)); + } + static void trivialConstructRange(T *dst, exint n) + { + if (!isPOD()) + { + for (exint i = 0; i < n; i++) + new (&dst[i]) T(); + } + else if (n == 1) + { + // Special case for n == 1. If the size parameter + // passed to memset is known at compile time, this + // function call will be inlined. This results in + // much faster performance than a real memset + // function call which is required in the case + // below, where n is not known until runtime. + // This makes calls to append() much faster. + memset((void *)dst, 0, sizeof(T)); + } + else + memset((void *)dst, 0, sizeof(T) * n); + } + + /// Element Destructor + static void trivialDestruct(T &dst) + { + if (!isPOD()) + dst.~T(); + } + static void trivialDestructRange(T *dst, exint n) + { + if (!isPOD()) + { + for (exint i = 0; i < n; i++) + dst[i].~T(); + } + } + +private: + /// Pointer to the array of elements of type T + T *myData; + + /// The number of elements for which we have allocated memory + exint myCapacity; + + /// The actual number of valid elements in the array + exint mySize; + + // The guts of the remove() methods. + inline exint removeAt(exint index); + + inline T * allocateCapacity(exint num_items); +}; +}} + + + +#endif // __UT_ARRAY_H_INCLUDED__ +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * This is meant to be included by UT_Array.h and includes + * the template implementations needed by external code. + */ + +#pragma once + +#ifndef __UT_ARRAYIMPL_H_INCLUDED__ +#define __UT_ARRAYIMPL_H_INCLUDED__ + + + + +#include +#include +#include +#include + +namespace igl { + /// @private + namespace FastWindingNumber { + +// Implemented in UT_Array.C +extern void ut_ArrayImplFree(void *p); + + +template +inline UT_Array::UT_Array(const UT_Array &a) + : myCapacity(a.size()), mySize(a.size()) +{ + if (myCapacity) + { + myData = allocateCapacity(myCapacity); + copyConstructRange(myData, a.array(), mySize); + } + else + { + myData = nullptr; + } +} + +template +inline UT_Array::UT_Array(std::initializer_list init) + : myCapacity(init.size()), mySize(init.size()) +{ + if (myCapacity) + { + myData = allocateCapacity(myCapacity); + copyConstructRange(myData, init.begin(), mySize); + } + else + { + myData = nullptr; + } +} + +template +inline UT_Array::UT_Array(UT_Array &&a) noexcept +{ + if (!a.isHeapBuffer()) + { + myData = nullptr; + myCapacity = 0; + mySize = 0; + operator=(std::move(a)); + return; + } + + myCapacity = a.myCapacity; + mySize = a.mySize; + myData = a.myData; + a.myCapacity = a.mySize = 0; + a.myData = nullptr; +} + + +template +inline UT_Array::~UT_Array() +{ + // NOTE: We call setCapacity to ensure that we call trivialDestructRange, + // then call free on myData. + setCapacity(0); +} + +template +inline T * +UT_Array::allocateCapacity(exint capacity) +{ + T *data = (T *)malloc(capacity * sizeof(T)); + // Avoid degenerate case if we happen to be aliased the wrong way + if (!isHeapBuffer(data)) + { + T *prev = data; + data = (T *)malloc(capacity * sizeof(T)); + ut_ArrayImplFree(prev); + } + return data; +} + +template +inline void +UT_Array::swap( UT_Array &other ) +{ + std::swap( myData, other.myData ); + std::swap( myCapacity, other.myCapacity ); + std::swap( mySize, other.mySize ); +} + + +template +inline exint +UT_Array::insert(exint index) +{ + if (index >= mySize) + { + bumpCapacity(index + 1); + + trivialConstructRange(myData + mySize, index - mySize + 1); + + mySize = index+1; + return index; + } + bumpCapacity(mySize + 1); + + UT_IGL_ASSERT_P(index >= 0); + ::memmove((void *)&myData[index+1], (void *)&myData[index], + ((mySize-index)*sizeof(T))); + + trivialConstruct(myData[index]); + + mySize++; + return index; +} + +template +template +inline exint +UT_Array::appendImpl(S &&s) +{ + if (mySize == myCapacity) + { + exint idx = safeIndex(s); + + // NOTE: UTbumpAlloc always returns a strictly larger value. + setCapacity(UTbumpAlloc(myCapacity)); + if (idx >= 0) + construct(myData[mySize], std::forward(myData[idx])); + else + construct(myData[mySize], std::forward(s)); + } + else + { + construct(myData[mySize], std::forward(s)); + } + return mySize++; +} + +template +template +inline exint +UT_Array::emplace_back(S&&... s) +{ + if (mySize == myCapacity) + setCapacity(UTbumpAlloc(myCapacity)); + + construct(myData[mySize], std::forward(s)...); + return mySize++; +} + +template +inline void +UT_Array::append(const T *pt, exint count) +{ + bumpCapacity(mySize + count); + copyConstructRange(myData + mySize, pt, count); + mySize += count; +} + +template +inline void +UT_Array::appendMultiple(const T &t, exint count) +{ + UT_IGL_ASSERT_P(count >= 0); + if (count <= 0) + return; + if (mySize + count >= myCapacity) + { + exint tidx = safeIndex(t); + + bumpCapacity(mySize + count); + + for (exint i = 0; i < count; i++) + copyConstruct(myData[mySize+i], tidx >= 0 ? myData[tidx] : t); + } + else + { + for (exint i = 0; i < count; i++) + copyConstruct(myData[mySize+i], t); + } + mySize += count; +} + +template +inline exint +UT_Array::concat(const UT_Array &a) +{ + bumpCapacity(mySize + a.mySize); + copyConstructRange(myData + mySize, a.myData, a.mySize); + mySize += a.mySize; + + return mySize; +} + +template +inline exint +UT_Array::multipleInsert(exint beg_index, exint count) +{ + exint end_index = beg_index + count; + + if (beg_index >= mySize) + { + bumpCapacity(end_index); + + trivialConstructRange(myData + mySize, end_index - mySize); + + mySize = end_index; + return beg_index; + } + bumpCapacity(mySize+count); + + ::memmove((void *)&myData[end_index], (void *)&myData[beg_index], + ((mySize-beg_index)*sizeof(T))); + mySize += count; + + trivialConstructRange(myData + beg_index, count); + + return beg_index; +} + +template +template +inline exint +UT_Array::insertImpl(S &&s, exint index) +{ + if (index == mySize) + { + // This case avoids an extraneous call to trivialConstructRange() + // which the compiler may not optimize out. + (void) appendImpl(std::forward(s)); + } + else if (index > mySize) + { + exint src_i = safeIndex(s); + + bumpCapacity(index + 1); + + trivialConstructRange(myData + mySize, index - mySize); + + if (src_i >= 0) + construct(myData[index], std::forward(myData[src_i])); + else + construct(myData[index], std::forward(s)); + + mySize = index + 1; + } + else // (index < mySize) + { + exint src_i = safeIndex(s); + + bumpCapacity(mySize + 1); + + ::memmove((void *)&myData[index+1], (void *)&myData[index], + ((mySize-index)*sizeof(T))); + + if (src_i >= index) + ++src_i; + + if (src_i >= 0) + construct(myData[index], std::forward(myData[src_i])); + else + construct(myData[index], std::forward(s)); + + ++mySize; + } + + return index; +} + +template +inline exint +UT_Array::removeAt(exint idx) +{ + trivialDestruct(myData[idx]); + if (idx != --mySize) + { + ::memmove((void *)&myData[idx], (void *)&myData[idx+1], + ((mySize-idx)*sizeof(T))); + } + + return idx; +} + +template +inline void +UT_Array::removeRange(exint begin_i, exint end_i) +{ + UT_IGL_ASSERT(begin_i <= end_i); + UT_IGL_ASSERT(end_i <= size()); + if (end_i < size()) + { + trivialDestructRange(myData + begin_i, end_i - begin_i); + ::memmove((void *)&myData[begin_i], (void *)&myData[end_i], + (mySize - end_i)*sizeof(T)); + } + setSize(mySize - (end_i - begin_i)); +} + +template +inline void +UT_Array::extractRange(exint begin_i, exint end_i, UT_Array& dest) +{ + UT_IGL_ASSERT_P(begin_i >= 0); + UT_IGL_ASSERT_P(begin_i <= end_i); + UT_IGL_ASSERT_P(end_i <= size()); + UT_IGL_ASSERT(this != &dest); + + exint nelements = end_i - begin_i; + + // grow the raw array if necessary. + dest.setCapacityIfNeeded(nelements); + + ::memmove((void*)dest.myData, (void*)&myData[begin_i], + nelements * sizeof(T)); + dest.mySize = nelements; + + // we just asserted this was true, but just in case + if (this != &dest) + { + if (end_i < size()) + { + ::memmove((void*)&myData[begin_i], (void*)&myData[end_i], + (mySize - end_i) * sizeof(T)); + } + setSize(mySize - nelements); + } +} + +template +inline void +UT_Array::move(exint srcIdx, exint destIdx, exint howMany) +{ + // Make sure all the parameters are valid. + if( srcIdx < 0 ) + srcIdx = 0; + if( destIdx < 0 ) + destIdx = 0; + // If we are told to move a set of elements that would extend beyond the + // end of the current array, trim the group. + if( srcIdx + howMany > size() ) + howMany = size() - srcIdx; + // If the destIdx would have us move the source beyond the end of the + // current array, move the destIdx back. + if( destIdx + howMany > size() ) + destIdx = size() - howMany; + if( srcIdx != destIdx && howMany > 0 ) + { + void **tmp = 0; + exint savelen; + + savelen = SYSabs(srcIdx - destIdx); + tmp = (void **)::malloc(savelen*sizeof(T)); + if( srcIdx > destIdx && howMany > 0 ) + { + // We're moving the group backwards. Save all the stuff that + // we would overwrite, plus everything beyond that to the + // start of the source group. Then move the source group, then + // tack the saved data onto the end of the moved group. + ::memcpy(tmp, (void *)&myData[destIdx], (savelen*sizeof(T))); + ::memmove((void *)&myData[destIdx], (void *)&myData[srcIdx], + (howMany*sizeof(T))); + ::memcpy((void *)&myData[destIdx+howMany], tmp, (savelen*sizeof(T))); + } + if( srcIdx < destIdx && howMany > 0 ) + { + // We're moving the group forwards. Save from the end of the + // group being moved to the end of the where the destination + // group will end up. Then copy the source to the destination. + // Then move back up to the original source location and drop + // in our saved data. + ::memcpy(tmp, (void *)&myData[srcIdx+howMany], (savelen*sizeof(T))); + ::memmove((void *)&myData[destIdx], (void *)&myData[srcIdx], + (howMany*sizeof(T))); + ::memcpy((void *)&myData[srcIdx], tmp, (savelen*sizeof(T))); + } + ::free(tmp); + } +} + +template +template +inline exint +UT_Array::removeIf(IsEqual is_equal) +{ + // Move dst to the first element to remove. + exint dst; + for (dst = 0; dst < mySize; dst++) + { + if (is_equal(myData[dst])) + break; + } + // Now start looking at all the elements past the first one to remove. + for (exint idx = dst+1; idx < mySize; idx++) + { + if (!is_equal(myData[idx])) + { + UT_IGL_ASSERT(idx != dst); + myData[dst] = myData[idx]; + dst++; + } + // On match, ignore. + } + // New size + mySize = dst; + return mySize; +} + +template +inline void +UT_Array::cycle(exint howMany) +{ + char *tempPtr; + exint numShift; // The number of items we shift + exint remaining; // mySize - numShift + + if (howMany == 0 || mySize < 1) return; + + numShift = howMany % (exint)mySize; + if (numShift < 0) numShift += mySize; + remaining = mySize - numShift; + tempPtr = new char[numShift*sizeof(T)]; + + ::memmove(tempPtr, (void *)&myData[remaining], (numShift * sizeof(T))); + ::memmove((void *)&myData[numShift], (void *)&myData[0], (remaining * sizeof(T))); + ::memmove((void *)&myData[0], tempPtr, (numShift * sizeof(T))); + + delete [] tempPtr; +} + +template +inline void +UT_Array::constant(const T &value) +{ + for (exint i = 0; i < mySize; i++) + { + myData[i] = value; + } +} + +template +inline void +UT_Array::zero() +{ + if (isPOD()) + ::memset((void *)myData, 0, mySize*sizeof(T)); + else + trivialConstructRange(myData, mySize); +} + +template +inline void +UT_Array::setCapacity(exint capacity) +{ + // Do nothing when new capacity is the same as the current + if (capacity == myCapacity) + return; + + // Special case for non-heap buffers + if (!isHeapBuffer()) + { + if (capacity < mySize) + { + // Destroy the extra elements without changing myCapacity + trivialDestructRange(myData + capacity, mySize - capacity); + mySize = capacity; + } + else if (capacity > myCapacity) + { + T *prev = myData; + myData = (T *)malloc(sizeof(T) * capacity); + // myData is safe because we're already a stack buffer + UT_IGL_ASSERT_P(isHeapBuffer()); + if (mySize > 0) + memcpy((void *)myData, (void *)prev, sizeof(T) * mySize); + myCapacity = capacity; + } + else + { + // Keep myCapacity unchanged in this case + UT_IGL_ASSERT_P(capacity >= mySize && capacity <= myCapacity); + } + return; + } + + if (capacity == 0) + { + if (myData) + { + trivialDestructRange(myData, mySize); + free(myData); + } + myData = 0; + myCapacity = 0; + mySize = 0; + return; + } + + if (capacity < mySize) + { + trivialDestructRange(myData + capacity, mySize - capacity); + mySize = capacity; + } + + if (myData) + myData = (T *)realloc(myData, capacity*sizeof(T)); + else + myData = (T *)malloc(sizeof(T) * capacity); + + // Avoid degenerate case if we happen to be aliased the wrong way + if (!isHeapBuffer()) + { + T *prev = myData; + myData = (T *)malloc(sizeof(T) * capacity); + if (mySize > 0) + memcpy((void *)myData, (void *)prev, sizeof(T) * mySize); + ut_ArrayImplFree(prev); + } + + myCapacity = capacity; + UT_IGL_ASSERT(myData); +} + +template +inline UT_Array & +UT_Array::operator=(const UT_Array &a) +{ + if (this == &a) + return *this; + + // Grow the raw array if necessary. + setCapacityIfNeeded(a.size()); + + // Make sure destructors and constructors are called on all elements + // being removed/added. + trivialDestructRange(myData, mySize); + copyConstructRange(myData, a.myData, a.size()); + + mySize = a.size(); + + return *this; +} + +template +inline UT_Array & +UT_Array::operator=(std::initializer_list a) +{ + const exint new_size = a.size(); + + // Grow the raw array if necessary. + setCapacityIfNeeded(new_size); + + // Make sure destructors and constructors are called on all elements + // being removed/added. + trivialDestructRange(myData, mySize); + + copyConstructRange(myData, a.begin(), new_size); + + mySize = new_size; + + return *this; +} + +template +inline UT_Array & +UT_Array::operator=(UT_Array &&a) +{ + if (!a.isHeapBuffer()) + { + // Cannot steal from non-heap buffers + clear(); + const exint n = a.size(); + setCapacityIfNeeded(n); + if (isPOD()) + { + if (n > 0) + memcpy(myData, a.myData, n * sizeof(T)); + } + else + { + for (exint i = 0; i < n; ++i) + new (&myData[i]) T(std::move(a.myData[i])); + } + mySize = a.mySize; + a.mySize = 0; + return *this; + } + // else, just steal even if we're a small buffer + + // Destroy all the elements we're currently holding. + if (myData) + { + trivialDestructRange(myData, mySize); + if (isHeapBuffer()) + ::free(myData); + } + + // Move the contents of the other array to us and empty the other container + // so that it destructs cleanly. + myCapacity = a.myCapacity; + mySize = a.mySize; + myData = a.myData; + a.myCapacity = a.mySize = 0; + a.myData = nullptr; + + return *this; +} + + +template +inline bool +UT_Array::operator==(const UT_Array &a) const +{ + if (this == &a) return true; + if (mySize != a.size()) return false; + for (exint i = 0; i < mySize; i++) + if (!(myData[i] == a(i))) return false; + return true; +} + +template +inline bool +UT_Array::operator!=(const UT_Array &a) const +{ + return (!operator==(a)); +} + +}} + +#endif // __UT_ARRAYIMPL_H_INCLUDED__ +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * Special case for arrays that are usually small, + * to avoid a heap allocation when the array really is small. + */ + +#pragma once + +#ifndef __UT_SMALLARRAY_H_INCLUDED__ +#define __UT_SMALLARRAY_H_INCLUDED__ + + + +#include +#include +namespace igl { + /// @private + namespace FastWindingNumber { + +/// An array class with the small buffer optimization, making it ideal for +/// cases when you know it will only contain a few elements at the expense of +/// increasing the object size by MAX_BYTES (subject to alignment). +template +class UT_SmallArray : public UT_Array +{ + // As many elements that fit into MAX_BYTES with 1 item minimum + enum { MAX_ELEMS = MAX_BYTES/sizeof(T) < 1 ? 1 : MAX_BYTES/sizeof(T) }; + +public: + +// gcc falsely warns about our use of offsetof() on non-POD types. We can't +// easily suppress this because it has to be done in the caller at +// instantiation time. Instead, punt to a runtime check instead. +#if defined(__clang__) || defined(_MSC_VER) + #define UT_SMALL_ARRAY_SIZE_IGL_ASSERT() \ + using ThisT = UT_SmallArray; \ + static_assert(offsetof(ThisT, myBuffer) == sizeof(UT_Array), \ + "In order for UT_Array's checks for whether it needs to free the buffer to work, " \ + "the buffer must be exactly following the base class memory.") +#else + #define UT_SMALL_ARRAY_SIZE_IGL_ASSERT() \ + UT_IGL_ASSERT_P(!UT_Array::isHeapBuffer()); +#endif + + /// Default construction + UT_SmallArray() + : UT_Array(/*capacity*/0) + { + UT_Array::unsafeShareData((T*)myBuffer, 0, MAX_ELEMS); + UT_SMALL_ARRAY_SIZE_IGL_ASSERT(); + } + + /// Copy constructor + /// @{ + explicit UT_SmallArray(const UT_Array ©) + : UT_Array(/*capacity*/0) + { + UT_Array::unsafeShareData((T*)myBuffer, 0, MAX_ELEMS); + UT_SMALL_ARRAY_SIZE_IGL_ASSERT(); + UT_Array::operator=(copy); + } + explicit UT_SmallArray(const UT_SmallArray ©) + : UT_Array(/*capacity*/0) + { + UT_Array::unsafeShareData((T*)myBuffer, 0, MAX_ELEMS); + UT_SMALL_ARRAY_SIZE_IGL_ASSERT(); + UT_Array::operator=(copy); + } + /// @} + + /// Move constructor + /// @{ + UT_SmallArray(UT_Array &&movable) noexcept + { + UT_Array::unsafeShareData((T*)myBuffer, 0, MAX_ELEMS); + UT_SMALL_ARRAY_SIZE_IGL_ASSERT(); + UT_Array::operator=(std::move(movable)); + } + UT_SmallArray(UT_SmallArray &&movable) noexcept + { + UT_Array::unsafeShareData((T*)myBuffer, 0, MAX_ELEMS); + UT_SMALL_ARRAY_SIZE_IGL_ASSERT(); + UT_Array::operator=(std::move(movable)); + } + /// @} + + /// Initializer list constructor + explicit UT_SmallArray(std::initializer_list init) + { + UT_Array::unsafeShareData((T*)myBuffer, 0, MAX_ELEMS); + UT_SMALL_ARRAY_SIZE_IGL_ASSERT(); + UT_Array::operator=(init); + } + +#undef UT_SMALL_ARRAY_SIZE_IGL_ASSERT + + /// Assignment operator + /// @{ + UT_SmallArray & + operator=(const UT_SmallArray ©) + { + UT_Array::operator=(copy); + return *this; + } + UT_SmallArray & + operator=(const UT_Array ©) + { + UT_Array::operator=(copy); + return *this; + } + /// @} + + /// Move operator + /// @{ + UT_SmallArray & + operator=(UT_SmallArray &&movable) + { + UT_Array::operator=(std::move(movable)); + return *this; + } + UT_SmallArray & + operator=(UT_Array &&movable) + { + UT_Array::operator=(std::move(movable)); + return *this; + } + /// @} + + UT_SmallArray & + operator=(std::initializer_list src) + { + UT_Array::operator=(src); + return *this; + } +private: + alignas(T) char myBuffer[MAX_ELEMS*sizeof(T)]; +}; +}} + +#endif // __UT_SMALLARRAY_H_INCLUDED__ +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * A vector class templated on its size and data type. + */ + +#pragma once + +#ifndef __UT_FixedVector__ +#define __UT_FixedVector__ + + + + +namespace igl { + /// @private + namespace FastWindingNumber { + +template +class UT_FixedVector +{ +public: + typedef UT_FixedVector ThisType; + typedef T value_type; + typedef T theType; + static const exint theSize = SIZE; + + T vec[SIZE]; + + SYS_FORCE_INLINE UT_FixedVector() = default; + + /// Initializes every component to the same value + SYS_FORCE_INLINE explicit UT_FixedVector(T that) noexcept + { + for (exint i = 0; i < SIZE; ++i) + vec[i] = that; + } + + SYS_FORCE_INLINE UT_FixedVector(const ThisType &that) = default; + SYS_FORCE_INLINE UT_FixedVector(ThisType &&that) = default; + + /// Converts vector of S into vector of T, + /// or just copies if same type. + template + SYS_FORCE_INLINE UT_FixedVector(const UT_FixedVector &that) noexcept + { + for (exint i = 0; i < SIZE; ++i) + vec[i] = that[i]; + } + + template + SYS_FORCE_INLINE UT_FixedVector(const S that[SIZE]) noexcept + { + for (exint i = 0; i < SIZE; ++i) + vec[i] = that[i]; + } + + SYS_FORCE_INLINE const T &operator[](exint i) const noexcept + { + UT_IGL_ASSERT_P(i >= 0 && i < SIZE); + return vec[i]; + } + SYS_FORCE_INLINE T &operator[](exint i) noexcept + { + UT_IGL_ASSERT_P(i >= 0 && i < SIZE); + return vec[i]; + } + + SYS_FORCE_INLINE constexpr const T *data() const noexcept + { + return vec; + } + SYS_FORCE_INLINE T *data() noexcept + { + return vec; + } + + SYS_FORCE_INLINE ThisType &operator=(const ThisType &that) = default; + SYS_FORCE_INLINE ThisType &operator=(ThisType &&that) = default; + + template + SYS_FORCE_INLINE ThisType &operator=(const UT_FixedVector &that) noexcept + { + for (exint i = 0; i < SIZE; ++i) + vec[i] = that[i]; + return *this; + } + SYS_FORCE_INLINE const ThisType &operator=(T that) noexcept + { + for (exint i = 0; i < SIZE; ++i) + vec[i] = that; + return *this; + } + template + SYS_FORCE_INLINE void operator+=(const UT_FixedVector &that) + { + for (exint i = 0; i < SIZE; ++i) + vec[i] += that[i]; + } + SYS_FORCE_INLINE void operator+=(T that) + { + for (exint i = 0; i < SIZE; ++i) + vec[i] += that; + } + template + SYS_FORCE_INLINE auto operator+(const UT_FixedVector &that) const -> UT_FixedVector + { + using Type = decltype(vec[0]+that[0]); + UT_FixedVector result; + for (exint i = 0; i < SIZE; ++i) + result[i] = vec[i] + that[i]; + return result; + } + template + SYS_FORCE_INLINE void operator-=(const UT_FixedVector &that) + { + for (exint i = 0; i < SIZE; ++i) + vec[i] -= that[i]; + } + SYS_FORCE_INLINE void operator-=(T that) + { + for (exint i = 0; i < SIZE; ++i) + vec[i] -= that; + } + template + SYS_FORCE_INLINE auto operator-(const UT_FixedVector &that) const -> UT_FixedVector + { + using Type = decltype(vec[0]-that[0]); + UT_FixedVector result; + for (exint i = 0; i < SIZE; ++i) + result[i] = vec[i] - that[i]; + return result; + } + template + SYS_FORCE_INLINE void operator*=(const UT_FixedVector &that) + { + for (exint i = 0; i < SIZE; ++i) + vec[i] *= that[i]; + } + template + SYS_FORCE_INLINE auto operator*(const UT_FixedVector &that) const -> UT_FixedVector + { + using Type = decltype(vec[0]*that[0]); + UT_FixedVector result; + for (exint i = 0; i < SIZE; ++i) + result[i] = vec[i] * that[i]; + return result; + } + SYS_FORCE_INLINE void operator*=(T that) + { + for (exint i = 0; i < SIZE; ++i) + vec[i] *= that; + } + SYS_FORCE_INLINE UT_FixedVector operator*(T that) const + { + UT_FixedVector result; + for (exint i = 0; i < SIZE; ++i) + result[i] = vec[i] * that; + return result; + } + template + SYS_FORCE_INLINE void operator/=(const UT_FixedVector &that) + { + for (exint i = 0; i < SIZE; ++i) + vec[i] /= that[i]; + } + template + SYS_FORCE_INLINE auto operator/(const UT_FixedVector &that) const -> UT_FixedVector + { + using Type = decltype(vec[0]/that[0]); + UT_FixedVector result; + for (exint i = 0; i < SIZE; ++i) + result[i] = vec[i] / that[i]; + return result; + } + + SYS_FORCE_INLINE void operator/=(T that) + { + if (std::is_integral::value) + { + for (exint i = 0; i < SIZE; ++i) + vec[i] /= that; + } + else + { + that = 1/that; + for (exint i = 0; i < SIZE; ++i) + vec[i] *= that; + } + } + SYS_FORCE_INLINE UT_FixedVector operator/(T that) const + { + UT_FixedVector result; + if (std::is_integral::value) + { + for (exint i = 0; i < SIZE; ++i) + result[i] = vec[i] / that; + } + else + { + that = 1/that; + for (exint i = 0; i < SIZE; ++i) + result[i] = vec[i] * that; + } + return result; + } + SYS_FORCE_INLINE void negate() + { + for (exint i = 0; i < SIZE; ++i) + vec[i] = -vec[i]; + } + + SYS_FORCE_INLINE UT_FixedVector operator-() const + { + UT_FixedVector result; + for (exint i = 0; i < SIZE; ++i) + result[i] = -vec[i]; + return result; + } + + template + SYS_FORCE_INLINE bool operator==(const UT_FixedVector &that) const noexcept + { + for (exint i = 0; i < SIZE; ++i) + { + if (vec[i] != T(that[i])) + return false; + } + return true; + } + template + SYS_FORCE_INLINE bool operator!=(const UT_FixedVector &that) const noexcept + { + return !(*this==that); + } + SYS_FORCE_INLINE bool isZero() const noexcept + { + for (exint i = 0; i < SIZE; ++i) + { + if (vec[i] != T(0)) + return false; + } + return true; + } + SYS_FORCE_INLINE T maxComponent() const + { + T v = vec[0]; + for (exint i = 1; i < SIZE; ++i) + v = (vec[i] > v) ? vec[i] : v; + return v; + } + SYS_FORCE_INLINE T minComponent() const + { + T v = vec[0]; + for (exint i = 1; i < SIZE; ++i) + v = (vec[i] < v) ? vec[i] : v; + return v; + } + SYS_FORCE_INLINE T avgComponent() const + { + T v = vec[0]; + for (exint i = 1; i < SIZE; ++i) + v += vec[i]; + return v / SIZE; + } + + SYS_FORCE_INLINE T length2() const noexcept + { + T a0(vec[0]); + T result(a0*a0); + for (exint i = 1; i < SIZE; ++i) + { + T ai(vec[i]); + result += ai*ai; + } + return result; + } + SYS_FORCE_INLINE T length() const + { + T len2 = length2(); + return SYSsqrt(len2); + } + template + SYS_FORCE_INLINE auto dot(const UT_FixedVector &that) const -> decltype(vec[0]*that[0]) + { + using TheType = decltype(vec[0]*that.vec[0]); + TheType result(vec[0]*that[0]); + for (exint i = 1; i < SIZE; ++i) + result += vec[i]*that[i]; + return result; + } + template + SYS_FORCE_INLINE auto distance2(const UT_FixedVector &that) const -> decltype(vec[0]-that[0]) + { + using TheType = decltype(vec[0]-that[0]); + TheType v(vec[0] - that[0]); + TheType result(v*v); + for (exint i = 1; i < SIZE; ++i) + { + v = vec[i] - that[i]; + result += v*v; + } + return result; + } + template + SYS_FORCE_INLINE auto distance(const UT_FixedVector &that) const -> decltype(vec[0]-that[0]) + { + auto dist2 = distance2(that); + return SYSsqrt(dist2); + } + + SYS_FORCE_INLINE T normalize() + { + T len2 = length2(); + if (len2 == T(0)) + return T(0); + if (len2 == T(1)) + return T(1); + T len = SYSsqrt(len2); + // Check if the square root is equal 1. sqrt(1+dx) ~ 1+dx/2, + // so it may get rounded to 1 when it wasn't 1 before. + if (len != T(1)) + (*this) /= len; + return len; + } +}; + +/// NOTE: Strictly speaking, this should use decltype(that*a[0]), +/// but in the interests of avoiding accidental precision escalation, +/// it uses T. +template +SYS_FORCE_INLINE UT_FixedVector operator*(const S &that,const UT_FixedVector &a) +{ + T t(that); + UT_FixedVector result; + for (exint i = 0; i < SIZE; ++i) + result[i] = t * a[i]; + return result; +} + +template +SYS_FORCE_INLINE auto +dot(const UT_FixedVector &a, const UT_FixedVector &b) -> decltype(a[0]*b[0]) +{ + return a.dot(b); +} + +template +SYS_FORCE_INLINE auto +SYSmin(const UT_FixedVector &a, const UT_FixedVector &b) -> UT_FixedVector +{ + using Type = decltype(a[0]+b[1]); + UT_FixedVector result; + for (exint i = 0; i < SIZE; ++i) + result[i] = SYSmin(Type(a[i]), Type(b[i])); + return result; +} + +template +SYS_FORCE_INLINE auto +SYSmax(const UT_FixedVector &a, const UT_FixedVector &b) -> UT_FixedVector +{ + using Type = decltype(a[0]+b[1]); + UT_FixedVector result; + for (exint i = 0; i < SIZE; ++i) + result[i] = SYSmax(Type(a[i]), Type(b[i])); + return result; +} + +template +struct UT_FixedVectorTraits +{ + typedef UT_FixedVector FixedVectorType; + typedef T DataType; + static const exint TupleSize = 1; + static const bool isVectorType = false; +}; + +template +struct UT_FixedVectorTraits > +{ + typedef UT_FixedVector FixedVectorType; + typedef T DataType; + static const exint TupleSize = SIZE; + static const bool isVectorType = true; +}; +}} + +#endif +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * Simple wrappers on tbb interface + */ + +#ifndef __UT_ParallelUtil__ +#define __UT_ParallelUtil__ + + + +#include // This is just included for std::thread::hardware_concurrency() +namespace igl { + /// @private + namespace FastWindingNumber { +namespace UT_Thread { inline int getNumProcessors() { + return std::thread::hardware_concurrency(); +}} + +//#include "tbb/blocked_range.h" +//#include "tbb/parallel_for.h" +////namespace tbb { class split; } +// +///// Declare prior to use. +//template +//using UT_BlockedRange = tbb::blocked_range; +// +//// Default implementation that calls range.size() +//template< typename RANGE > +//struct UT_EstimatorNumItems +//{ +// UT_EstimatorNumItems() {} +// +// size_t operator()(const RANGE& range) const +// { +// return range.size(); +// } +//}; +// +///// This is needed by UT_CoarsenedRange +//template +//inline size_t UTestimatedNumItems(const RANGE& range) +//{ +// return UT_EstimatorNumItems()(range); +//} +// +///// UT_CoarsenedRange: This should be used only inside +///// UT_ParallelFor and UT_ParallelReduce +///// This class wraps an existing range with a new range. +///// This allows us to use simple_partitioner, rather than +///// auto_partitioner, which has disastrous performance with +///// the default grain size in ttb 4. +//template< typename RANGE > +//class UT_CoarsenedRange : public RANGE +//{ +//public: +// // Compiler-generated versions are fine: +// // ~UT_CoarsenedRange(); +// // UT_CoarsenedRange(const UT_CoarsenedRange&); +// +// // Split into two sub-ranges: +// UT_CoarsenedRange(UT_CoarsenedRange& range, tbb::split spl) : +// RANGE(range, spl), +// myGrainSize(range.myGrainSize) +// { +// } +// +// // Inherited: bool empty() const +// +// bool is_divisible() const +// { +// return +// RANGE::is_divisible() && +// (UTestimatedNumItems(static_cast(*this)) > myGrainSize); +// } +// +//private: +// size_t myGrainSize; +// +// UT_CoarsenedRange(const RANGE& base_range, const size_t grain_size) : +// RANGE(base_range), +// myGrainSize(grain_size) +// { +// } +// +// template +// friend void UTparallelFor( +// const Range &range, const Body &body, +// const int subscribe_ratio, const int min_grain_size +// ); +//}; +// +///// Run the @c body function over a range in parallel. +///// UTparallelFor attempts to spread the range out over at most +///// subscribe_ratio * num_processor tasks. +///// The factor subscribe_ratio can be used to help balance the load. +///// UTparallelFor() uses tbb for its implementation. +///// The used grain size is the maximum of min_grain_size and +///// if UTestimatedNumItems(range) / (subscribe_ratio * num_processor). +///// If subscribe_ratio == 0, then a grain size of min_grain_size will be used. +///// A range can be split only when UTestimatedNumItems(range) exceeds the +///// grain size the range is divisible. +// +///// +///// Requirements for the Range functor are: +///// - the requirements of the tbb Range Concept +///// - UT_estimatorNumItems must return the estimated number of work items +///// for the range. When Range::size() is not the correct estimate, then a +///// (partial) specialization of UT_estimatorNumItemsimatorRange must be provided +///// for the type Range. +///// +///// Requirements for the Body function are: +///// - @code Body(const Body &); @endcode @n +///// Copy Constructor +///// - @code Body()::~Body(); @endcode @n +///// Destructor +///// - @code void Body::operator()(const Range &range) const; @endcode +///// Function call to perform operation on the range. Note the operator is +///// @b const. +///// +///// The requirements for a Range object are: +///// - @code Range::Range(const Range&); @endcode @n +///// Copy constructor +///// - @code Range::~Range(); @endcode @n +///// Destructor +///// - @code bool Range::is_divisible() const; @endcode @n +///// True if the range can be partitioned into two sub-ranges +///// - @code bool Range::empty() const; @endcode @n +///// True if the range is empty +///// - @code Range::Range(Range &r, UT_Split) const; @endcode @n +///// Split the range @c r into two sub-ranges (i.e. modify @c r and *this) +///// +///// Example: @code +///// class Square { +///// public: +///// Square(double *data) : myData(data) {} +///// ~Square(); +///// void operator()(const UT_BlockedRange &range) const +///// { +///// for (int64 i = range.begin(); i != range.end(); ++i) +///// myData[i] *= myData[i]; +///// } +///// double *myData; +///// }; +///// ... +///// +///// void +///// parallel_square(double *array, int64 length) +///// { +///// UTparallelFor(UT_BlockedRange(0, length), Square(array)); +///// } +///// @endcode +///// +///// @see UTparallelReduce(), UT_BlockedRange() +// +//template +//void UTparallelFor( +// const Range &range, const Body &body, +// const int subscribe_ratio = 2, +// const int min_grain_size = 1 +//) +//{ +// const size_t num_processors( UT_Thread::getNumProcessors() ); +// +// UT_IGL_ASSERT( num_processors >= 1 ); +// UT_IGL_ASSERT( min_grain_size >= 1 ); +// UT_IGL_ASSERT( subscribe_ratio >= 0 ); +// +// const size_t est_range_size( UTestimatedNumItems(range) ); +// +// // Don't run on an empty range! +// if (est_range_size == 0) +// return; +// +// // Avoid tbb overhead if entire range needs to be single threaded +// if (num_processors == 1 || est_range_size <= min_grain_size) +// { +// body(range); +// return; +// } +// +// size_t grain_size(min_grain_size); +// if( subscribe_ratio > 0 ) +// grain_size = std::max( +// grain_size, +// est_range_size / (subscribe_ratio * num_processors) +// ); +// +// UT_CoarsenedRange< Range > coarsened_range(range, grain_size); +// +// tbb::parallel_for(coarsened_range, body, tbb::simple_partitioner()); +//} +// +///// Version of UTparallelFor that is tuned for the case where the range +///// consists of lightweight items, for example, +///// float additions or matrix-vector multiplications. +//template +//void +//UTparallelForLightItems(const Range &range, const Body &body) +//{ +// UTparallelFor(range, body, 2, 1024); +//} +// +///// UTserialFor can be used as a debugging tool to quickly replace a parallel +///// for with a serial for. +//template +//void UTserialFor(const Range &range, const Body &body) +// { body(range); } +// +}} +#endif +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * Bounding Volume Hierarchy (BVH) implementation. + * To call functions not implemented here, also include UT_BVHImpl.h + */ + +#pragma once + +#ifndef __HDK_UT_BVH_h__ +#define __HDK_UT_BVH_h__ + + + + +#include +#include +namespace igl { + /// @private + namespace FastWindingNumber { + +template class UT_Array; +class v4uf; +class v4uu; + +namespace HDK_Sample { + +namespace UT { + +template +struct Box { + T vals[NAXES][2]; + + SYS_FORCE_INLINE Box() noexcept = default; + SYS_FORCE_INLINE constexpr Box(const Box &other) noexcept = default; + SYS_FORCE_INLINE constexpr Box(Box &&other) noexcept = default; + SYS_FORCE_INLINE Box& operator=(const Box &other) noexcept = default; + SYS_FORCE_INLINE Box& operator=(Box &&other) noexcept = default; + + template + SYS_FORCE_INLINE Box(const Box& other) noexcept { + static_assert( + (std::is_standard_layout>::value && std::is_trivially_copyable>::value && std::is_trivially_default_constructible>::value) || + !(std::is_standard_layout::value && std::is_trivially_copyable::value && std::is_trivially_default_constructible::value), + "UT::Box should be POD, for better performance in UT_Array, etc."); + + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = T(other.vals[axis][0]); + vals[axis][1] = T(other.vals[axis][1]); + } + } + template + SYS_FORCE_INLINE Box(const UT_FixedVector& pt) noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = pt[axis]; + vals[axis][1] = pt[axis]; + } + } + template + SYS_FORCE_INLINE Box& operator=(const Box& other) noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = T(other.vals[axis][0]); + vals[axis][1] = T(other.vals[axis][1]); + } + return *this; + } + + SYS_FORCE_INLINE const T* operator[](const size_t axis) const noexcept { + UT_IGL_ASSERT_P(axis < NAXES); + return vals[axis]; + } + SYS_FORCE_INLINE T* operator[](const size_t axis) noexcept { + UT_IGL_ASSERT_P(axis < NAXES); + return vals[axis]; + } + + SYS_FORCE_INLINE void initBounds() noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = std::numeric_limits::max(); + vals[axis][1] = -std::numeric_limits::max(); + } + } + /// Copy the source box. + /// NOTE: This is so that in templated code that may have a Box or a + /// UT_FixedVector, it can call initBounds and still work. + SYS_FORCE_INLINE void initBounds(const Box& src) noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = src.vals[axis][0]; + vals[axis][1] = src.vals[axis][1]; + } + } + /// Initialize with the union of the source boxes. + /// NOTE: This is so that in templated code that may have Box's or a + /// UT_FixedVector's, it can call initBounds and still work. + SYS_FORCE_INLINE void initBoundsUnordered(const Box& src0, const Box& src1) noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = SYSmin(src0.vals[axis][0], src1.vals[axis][0]); + vals[axis][1] = SYSmax(src0.vals[axis][1], src1.vals[axis][1]); + } + } + SYS_FORCE_INLINE void combine(const Box& src) noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + T& minv = vals[axis][0]; + T& maxv = vals[axis][1]; + const T curminv = src.vals[axis][0]; + const T curmaxv = src.vals[axis][1]; + minv = (minv < curminv) ? minv : curminv; + maxv = (maxv > curmaxv) ? maxv : curmaxv; + } + } + SYS_FORCE_INLINE void enlargeBounds(const Box& src) noexcept { + combine(src); + } + + template + SYS_FORCE_INLINE + void initBounds(const UT_FixedVector& pt) noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = pt[axis]; + vals[axis][1] = pt[axis]; + } + } + template + SYS_FORCE_INLINE + void initBounds(const UT_FixedVector& min, const UT_FixedVector& max) noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = min[axis]; + vals[axis][1] = max[axis]; + } + } + template + SYS_FORCE_INLINE + void initBoundsUnordered(const UT_FixedVector& p0, const UT_FixedVector& p1) noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = SYSmin(p0[axis], p1[axis]); + vals[axis][1] = SYSmax(p0[axis], p1[axis]); + } + } + template + SYS_FORCE_INLINE + void enlargeBounds(const UT_FixedVector& pt) noexcept { + for (uint axis = 0; axis < NAXES; ++axis) { + vals[axis][0] = SYSmin(vals[axis][0], pt[axis]); + vals[axis][1] = SYSmax(vals[axis][1], pt[axis]); + } + } + + SYS_FORCE_INLINE + UT_FixedVector getMin() const noexcept { + UT_FixedVector v; + for (uint axis = 0; axis < NAXES; ++axis) { + v[axis] = vals[axis][0]; + } + return v; + } + + SYS_FORCE_INLINE + UT_FixedVector getMax() const noexcept { + UT_FixedVector v; + for (uint axis = 0; axis < NAXES; ++axis) { + v[axis] = vals[axis][1]; + } + return v; + } + + T diameter2() const noexcept { + T diff = (vals[0][1]-vals[0][0]); + T sum = diff*diff; + for (uint axis = 1; axis < NAXES; ++axis) { + diff = (vals[axis][1]-vals[axis][0]); + sum += diff*diff; + } + return sum; + } + T volume() const noexcept { + T product = (vals[0][1]-vals[0][0]); + for (uint axis = 1; axis < NAXES; ++axis) { + product *= (vals[axis][1]-vals[axis][0]); + } + return product; + } + T half_surface_area() const noexcept { + if (NAXES==1) { + // NOTE: Although this should technically be 1, + // that doesn't make any sense as a heuristic, + // so we fall back to the "volume" of this box. + return (vals[0][1]-vals[0][0]); + } + if (NAXES==2) { + const T d0 = (vals[0][1]-vals[0][0]); + const T d1 = (vals[1][1]-vals[1][0]); + return d0 + d1; + } + if (NAXES==3) { + const T d0 = (vals[0][1]-vals[0][0]); + const T d1 = (vals[1][1]-vals[1][0]); + const T d2 = (vals[2][1]-vals[2][0]); + return d0*d1 + d1*d2 + d2*d0; + } + if (NAXES==4) { + const T d0 = (vals[0][1]-vals[0][0]); + const T d1 = (vals[1][1]-vals[1][0]); + const T d2 = (vals[2][1]-vals[2][0]); + const T d3 = (vals[3][1]-vals[3][0]); + // This is just d0d1d2 + d1d2d3 + d2d3d0 + d3d0d1 refactored. + const T d0d1 = d0*d1; + const T d2d3 = d2*d3; + return d0d1*(d2+d3) + d2d3*(d0+d1); + } + + T sum = 0; + for (uint skipped_axis = 0; skipped_axis < NAXES; ++skipped_axis) { + T product = 1; + for (uint axis = 0; axis < NAXES; ++axis) { + if (axis != skipped_axis) { + product *= (vals[axis][1]-vals[axis][0]); + } + } + sum += product; + } + return sum; + } + T axis_sum() const noexcept { + T sum = (vals[0][1]-vals[0][0]); + for (uint axis = 1; axis < NAXES; ++axis) { + sum += (vals[axis][1]-vals[axis][0]); + } + return sum; + } + template + SYS_FORCE_INLINE void intersect( + T &box_tmin, + T &box_tmax, + const UT_FixedVector &signs, + const UT_FixedVector &origin, + const UT_FixedVector &inverse_direction + ) const noexcept { + for (int axis = 0; axis < NAXES; ++axis) + { + uint sign = signs[axis]; + T t1 = (vals[axis][sign] - origin[axis]) * inverse_direction[axis]; + T t2 = (vals[axis][sign^1] - origin[axis]) * inverse_direction[axis]; + box_tmin = SYSmax(t1, box_tmin); + box_tmax = SYSmin(t2, box_tmax); + } + } + SYS_FORCE_INLINE void intersect(const Box& other, Box& dest) const noexcept { + for (int axis = 0; axis < NAXES; ++axis) + { + dest.vals[axis][0] = SYSmax(vals[axis][0], other.vals[axis][0]); + dest.vals[axis][1] = SYSmin(vals[axis][1], other.vals[axis][1]); + } + } + template + SYS_FORCE_INLINE T minDistance2( + const UT_FixedVector &p + ) const noexcept { + T diff = SYSmax(SYSmax(vals[0][0]-p[0], p[0]-vals[0][1]), T(0.0f)); + T d2 = diff*diff; + for (int axis = 1; axis < NAXES; ++axis) + { + diff = SYSmax(SYSmax(vals[axis][0]-p[axis], p[axis]-vals[axis][1]), T(0.0f)); + d2 += diff*diff; + } + return d2; + } + template + SYS_FORCE_INLINE T maxDistance2( + const UT_FixedVector &p + ) const noexcept { + T diff = SYSmax(p[0]-vals[0][0], vals[0][1]-p[0]); + T d2 = diff*diff; + for (int axis = 1; axis < NAXES; ++axis) + { + diff = SYSmax(p[axis]-vals[axis][0], vals[axis][1]-p[axis]); + d2 += diff*diff; + } + return d2; + } +}; + +/// Used by BVH::init to specify the heuristic to use for choosing between different box splits. +/// I tried putting this inside the BVH class, but I had difficulty getting it to compile. +enum class BVH_Heuristic { + /// Tries to minimize the sum of axis lengths of the boxes. + /// This is useful for applications where the probability of a box being applicable to a + /// query is proportional to the "length", e.g. the probability of a random infinite plane + /// intersecting the box. + BOX_PERIMETER, + + /// Tries to minimize the "surface area" of the boxes. + /// In 3D, uses the surface area; in 2D, uses the perimeter; in 1D, uses the axis length. + /// This is what most applications, e.g. ray tracing, should use, particularly when the + /// probability of a box being applicable to a query is proportional to the surface "area", + /// e.g. the probability of a random ray hitting the box. + /// + /// NOTE: USE THIS ONE IF YOU ARE UNSURE! + BOX_AREA, + + /// Tries to minimize the "volume" of the boxes. + /// Uses the product of all axis lengths as a heuristic, (volume in 3D, area in 2D, length in 1D). + /// This is useful for applications where the probability of a box being applicable to a + /// query is proportional to the "volume", e.g. the probability of a random point being inside the box. + BOX_VOLUME, + + /// Tries to minimize the "radii" of the boxes (i.e. the distance from the centre to a corner). + /// This is useful for applications where the probability of a box being applicable to a + /// query is proportional to the distance to the box centre, e.g. the probability of a random + /// infinite plane being within the "radius" of the centre. + BOX_RADIUS, + + /// Tries to minimize the squared "radii" of the boxes (i.e. the squared distance from the centre to a corner). + /// This is useful for applications where the probability of a box being applicable to a + /// query is proportional to the squared distance to the box centre, e.g. the probability of a random + /// ray passing within the "radius" of the centre. + BOX_RADIUS2, + + /// Tries to minimize the cubed "radii" of the boxes (i.e. the cubed distance from the centre to a corner). + /// This is useful for applications where the probability of a box being applicable to a + /// query is proportional to the cubed distance to the box centre, e.g. the probability of a random + /// point being within the "radius" of the centre. + BOX_RADIUS3, + + /// Tries to minimize the depth of the tree by primarily splitting at the median of the max axis. + /// It may fall back to minimizing the area, but the tree depth should be unaffected. + /// + /// FIXME: This is not fully implemented yet. + MEDIAN_MAX_AXIS +}; + +template +class BVH { +public: + using INT_TYPE = uint; + struct Node { + INT_TYPE child[N]; + + static constexpr INT_TYPE theN = N; + static constexpr INT_TYPE EMPTY = INT_TYPE(-1); + static constexpr INT_TYPE INTERNAL_BIT = (INT_TYPE(1)<<(sizeof(INT_TYPE)*8 - 1)); + SYS_FORCE_INLINE static INT_TYPE markInternal(INT_TYPE internal_node_num) noexcept { + return internal_node_num | INTERNAL_BIT; + } + SYS_FORCE_INLINE static bool isInternal(INT_TYPE node_int) noexcept { + return (node_int & INTERNAL_BIT) != 0; + } + SYS_FORCE_INLINE static INT_TYPE getInternalNum(INT_TYPE node_int) noexcept { + return node_int & ~INTERNAL_BIT; + } + }; +private: + struct FreeDeleter { + SYS_FORCE_INLINE void operator()(Node* p) const { + if (p) { + // The pointer was allocated with malloc by UT_Array, + // so it must be freed with free. + free(p); + } + } + }; + + std::unique_ptr myRoot; + INT_TYPE myNumNodes; +public: + SYS_FORCE_INLINE BVH() noexcept : myRoot(nullptr), myNumNodes(0) {} + + template + inline void init(const BOX_TYPE* boxes, const INT_TYPE nboxes, SRC_INT_TYPE* indices=nullptr, bool reorder_indices=false, INT_TYPE max_items_per_leaf=1) noexcept; + + template + inline void init(Box axes_minmax, const BOX_TYPE* boxes, INT_TYPE nboxes, SRC_INT_TYPE* indices=nullptr, bool reorder_indices=false, INT_TYPE max_items_per_leaf=1) noexcept; + + SYS_FORCE_INLINE + INT_TYPE getNumNodes() const noexcept + { + return myNumNodes; + } + SYS_FORCE_INLINE + const Node *getNodes() const noexcept + { + return myRoot.get(); + } + + SYS_FORCE_INLINE + void clear() noexcept { + myRoot.reset(); + myNumNodes = 0; + } + + /// For each node, this effectively does: + /// LOCAL_DATA local_data[MAX_ORDER]; + /// bool descend = functors.pre(nodei, parent_data); + /// if (!descend) + /// return; + /// for each child { + /// if (isitem(child)) + /// functors.item(getitemi(child), nodei, local_data[child]); + /// else if (isnode(child)) + /// recurse(getnodei(child), local_data); + /// } + /// functors.post(nodei, parent_nodei, data_for_parent, num_children, local_data); + template + inline void traverse( + FUNCTORS &functors, + LOCAL_DATA *data_for_parent=nullptr) const noexcept; + + /// This acts like the traverse function, except if the number of nodes in two subtrees + /// of a node contain at least parallel_threshold nodes, they may be executed in parallel. + /// If parallel_threshold is 0, even item_functor may be executed on items in parallel. + /// NOTE: Make sure that your functors don't depend on the order that they're executed in, + /// e.g. don't add values from sibling nodes together except in post functor, + /// else they might have nondeterministic roundoff or miss some values entirely. + template + inline void traverseParallel( + INT_TYPE parallel_threshold, + FUNCTORS &functors, + LOCAL_DATA *data_for_parent=nullptr) const noexcept; + + /// For each node, this effectively does: + /// LOCAL_DATA local_data[MAX_ORDER]; + /// uint descend = functors.pre(nodei, parent_data); + /// if (!descend) + /// return; + /// for each child { + /// if (!(descend & (1< + inline void traverseVector( + FUNCTORS &functors, + LOCAL_DATA *data_for_parent=nullptr) const noexcept; + + /// Prints a text representation of the tree to stdout. + inline void debugDump() const; + + template + static inline void createTrivialIndices(SRC_INT_TYPE* indices, const INT_TYPE n) noexcept; + +private: + template + inline void traverseHelper( + INT_TYPE nodei, + INT_TYPE parent_nodei, + FUNCTORS &functors, + LOCAL_DATA *data_for_parent=nullptr) const noexcept; + + template + inline void traverseParallelHelper( + INT_TYPE nodei, + INT_TYPE parent_nodei, + INT_TYPE parallel_threshold, + INT_TYPE next_node_id, + FUNCTORS &functors, + LOCAL_DATA *data_for_parent=nullptr) const noexcept; + + template + inline void traverseVectorHelper( + INT_TYPE nodei, + INT_TYPE parent_nodei, + FUNCTORS &functors, + LOCAL_DATA *data_for_parent=nullptr) const noexcept; + + template + static inline void computeFullBoundingBox(Box& axes_minmax, const BOX_TYPE* boxes, const INT_TYPE nboxes, SRC_INT_TYPE* indices) noexcept; + + template + static inline void initNode(UT_Array& nodes, Node &node, const Box& axes_minmax, const BOX_TYPE* boxes, SRC_INT_TYPE* indices, const INT_TYPE nboxes) noexcept; + + template + static inline void initNodeReorder(UT_Array& nodes, Node &node, const Box& axes_minmax, const BOX_TYPE* boxes, SRC_INT_TYPE* indices, const INT_TYPE nboxes, const INT_TYPE indices_offset, const INT_TYPE max_items_per_leaf) noexcept; + + template + static inline void multiSplit(const Box& axes_minmax, const BOX_TYPE* boxes, SRC_INT_TYPE* indices, INT_TYPE nboxes, SRC_INT_TYPE* sub_indices[N+1], Box sub_boxes[N]) noexcept; + + template + static inline void split(const Box& axes_minmax, const BOX_TYPE* boxes, SRC_INT_TYPE* indices, INT_TYPE nboxes, SRC_INT_TYPE*& split_indices, Box* split_boxes) noexcept; + + template + static inline void adjustParallelChildNodes(INT_TYPE nparallel, UT_Array& nodes, Node& node, UT_Array* parallel_nodes, SRC_INT_TYPE* sub_indices) noexcept; + + template + static inline void nthElement(const BOX_TYPE* boxes, SRC_INT_TYPE* indices, const SRC_INT_TYPE* indices_end, const uint axis, SRC_INT_TYPE*const nth) noexcept; + + template + static inline void partitionByCentre(const BOX_TYPE* boxes, SRC_INT_TYPE*const indices, const SRC_INT_TYPE*const indices_end, const uint axis, const T pivotx2, SRC_INT_TYPE*& ppivot_start, SRC_INT_TYPE*& ppivot_end) noexcept; + + /// An overestimate of the number of nodes needed. + /// At worst, we could have only 2 children in every leaf, and + /// then above that, we have a geometric series with r=1/N and a=(sub_nboxes/2)/N + /// The true worst case might be a little worst than this, but + /// it's probably fairly unlikely. + SYS_FORCE_INLINE static INT_TYPE nodeEstimate(const INT_TYPE nboxes) noexcept { + return nboxes/2 + nboxes/(2*(N-1)); + } + + template + SYS_FORCE_INLINE static T unweightedHeuristic(const Box& box) noexcept { + if (H == BVH_Heuristic::BOX_PERIMETER) { + return box.axis_sum(); + } + if (H == BVH_Heuristic::BOX_AREA) { + return box.half_surface_area(); + } + if (H == BVH_Heuristic::BOX_VOLUME) { + return box.volume(); + } + if (H == BVH_Heuristic::BOX_RADIUS) { + T diameter2 = box.diameter2(); + return SYSsqrt(diameter2); + } + if (H == BVH_Heuristic::BOX_RADIUS2) { + return box.diameter2(); + } + if (H == BVH_Heuristic::BOX_RADIUS3) { + T diameter2 = box.diameter2(); + return diameter2*SYSsqrt(diameter2); + } + UT_IGL_ASSERT_MSG(0, "BVH_Heuristic::MEDIAN_MAX_AXIS should be handled separately by caller!"); + return T(1); + } + + /// 16 equal-length spans (15 evenly-spaced splits) should be enough for a decent heuristic + static constexpr INT_TYPE NSPANS = 16; + static constexpr INT_TYPE NSPLITS = NSPANS-1; + + /// At least 1/16 of all boxes must be on each side, else we could end up with a very deep tree + static constexpr INT_TYPE MIN_FRACTION = 16; +}; + +} // UT namespace + +template +using UT_BVH = UT::BVH; + +} // End HDK_Sample namespace +}} +#endif +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * Bounding Volume Hierarchy (BVH) implementation. + * The main file is UT_BVH.h; this file is separate so that + * files that don't actually need to call functions on the BVH + * won't have unnecessary headers and functions included. + */ + +#pragma once + +#ifndef __HDK_UT_BVHImpl_h__ +#define __HDK_UT_BVHImpl_h__ + + + + + + + + +#include "parallel_for.h" + +#include +#include + +namespace igl { + /// @private + namespace FastWindingNumber { +namespace HDK_Sample { + +namespace UT { + +template +SYS_FORCE_INLINE bool utBoxExclude(const UT::Box& box) noexcept { + bool has_nan_or_inf = !SYSisFinite(box[0][0]); + has_nan_or_inf |= !SYSisFinite(box[0][1]); + for (uint axis = 1; axis < NAXES; ++axis) + { + has_nan_or_inf |= !SYSisFinite(box[axis][0]); + has_nan_or_inf |= !SYSisFinite(box[axis][1]); + } + return has_nan_or_inf; +} +template +SYS_FORCE_INLINE bool utBoxExclude(const UT::Box& box) noexcept { + const int32 *pboxints = reinterpret_cast(&box); + // Fast check for NaN or infinity: check if exponent bits are 0xFF. + bool has_nan_or_inf = ((pboxints[0] & 0x7F800000) == 0x7F800000); + has_nan_or_inf |= ((pboxints[1] & 0x7F800000) == 0x7F800000); + for (uint axis = 1; axis < NAXES; ++axis) + { + has_nan_or_inf |= ((pboxints[2*axis] & 0x7F800000) == 0x7F800000); + has_nan_or_inf |= ((pboxints[2*axis + 1] & 0x7F800000) == 0x7F800000); + } + return has_nan_or_inf; +} +template +SYS_FORCE_INLINE T utBoxCenter(const UT::Box& box, uint axis) noexcept { + const T* v = box.vals[axis]; + return v[0] + v[1]; +} +template +struct ut_BoxCentre { + constexpr static uint scale = 2; +}; +template +SYS_FORCE_INLINE T utBoxExclude(const UT_FixedVector& position) noexcept { + bool has_nan_or_inf = !SYSisFinite(position[0]); + for (uint axis = 1; axis < NAXES; ++axis) + has_nan_or_inf |= !SYSisFinite(position[axis]); + return has_nan_or_inf; +} +template +SYS_FORCE_INLINE bool utBoxExclude(const UT_FixedVector& position) noexcept { + const int32 *ppositionints = reinterpret_cast(&position); + // Fast check for NaN or infinity: check if exponent bits are 0xFF. + bool has_nan_or_inf = ((ppositionints[0] & 0x7F800000) == 0x7F800000); + for (uint axis = 1; axis < NAXES; ++axis) + has_nan_or_inf |= ((ppositionints[axis] & 0x7F800000) == 0x7F800000); + return has_nan_or_inf; +} +template +SYS_FORCE_INLINE T utBoxCenter(const UT_FixedVector& position, uint axis) noexcept { + return position[axis]; +} +template +struct ut_BoxCentre> { + constexpr static uint scale = 1; +}; + +template +inline INT_TYPE utExcludeNaNInfBoxIndices(const BOX_TYPE* boxes, SRC_INT_TYPE* indices, INT_TYPE& nboxes) noexcept +{ + //constexpr INT_TYPE PARALLEL_THRESHOLD = 65536; + //INT_TYPE ntasks = 1; + //if (nboxes >= PARALLEL_THRESHOLD) + //{ + // INT_TYPE nprocessors = UT_Thread::getNumProcessors(); + // ntasks = (nprocessors > 1) ? SYSmin(4*nprocessors, nboxes/(PARALLEL_THRESHOLD/2)) : 1; + //} + //if (ntasks == 1) + { + // Serial: easy case; just loop through. + + const SRC_INT_TYPE* indices_end = indices + nboxes; + + // Loop through forward once + SRC_INT_TYPE* psrc_index = indices; + for (; psrc_index != indices_end; ++psrc_index) + { + const bool exclude = utBoxExclude(boxes[*psrc_index]); + if (exclude) + break; + } + if (psrc_index == indices_end) + return 0; + + // First NaN or infinite box + SRC_INT_TYPE* nan_start = psrc_index; + for (++psrc_index; psrc_index != indices_end; ++psrc_index) + { + const bool exclude = utBoxExclude(boxes[*psrc_index]); + if (!exclude) + { + *nan_start = *psrc_index; + ++nan_start; + } + } + nboxes = nan_start-indices; + return indices_end - nan_start; + } + +} + +template +template +inline void BVH::init(const BOX_TYPE* boxes, const INT_TYPE nboxes, SRC_INT_TYPE* indices, bool reorder_indices, INT_TYPE max_items_per_leaf) noexcept { + Box axes_minmax; + computeFullBoundingBox(axes_minmax, boxes, nboxes, indices); + + init(axes_minmax, boxes, nboxes, indices, reorder_indices, max_items_per_leaf); +} + +template +template +inline void BVH::init(Box axes_minmax, const BOX_TYPE* boxes, INT_TYPE nboxes, SRC_INT_TYPE* indices, bool reorder_indices, INT_TYPE max_items_per_leaf) noexcept { + // Clear the tree in advance to save memory. + myRoot.reset(); + + if (nboxes == 0) { + myNumNodes = 0; + return; + } + + UT_Array local_indices; + if (!indices) { + local_indices.setSizeNoInit(nboxes); + indices = local_indices.array(); + createTrivialIndices(indices, nboxes); + } + + // Exclude any boxes with NaNs or infinities by shifting down indices + // over the bad box indices and updating nboxes. + INT_TYPE nexcluded = utExcludeNaNInfBoxIndices(boxes, indices, nboxes); + if (nexcluded != 0) { + if (nboxes == 0) { + myNumNodes = 0; + return; + } + computeFullBoundingBox(axes_minmax, boxes, nboxes, indices); + } + + UT_Array nodes; + // Preallocate an overestimate of the number of nodes needed. + nodes.setCapacity(nodeEstimate(nboxes)); + nodes.setSize(1); + if (reorder_indices) + initNodeReorder(nodes, nodes[0], axes_minmax, boxes, indices, nboxes, 0, max_items_per_leaf); + else + initNode(nodes, nodes[0], axes_minmax, boxes, indices, nboxes); + + // If capacity is more than 12.5% over the size, rellocate. + if (8*nodes.capacity() > 9*nodes.size()) { + nodes.setCapacity(nodes.size()); + } + // Steal ownership of the array from the UT_Array + myRoot.reset(nodes.array()); + myNumNodes = nodes.size(); + nodes.unsafeClearData(); +} + +template +template +inline void BVH::traverse( + FUNCTORS &functors, + LOCAL_DATA* data_for_parent) const noexcept +{ + if (!myRoot) + return; + + // NOTE: The root is always index 0. + traverseHelper(0, INT_TYPE(-1), functors, data_for_parent); +} +template +template +inline void BVH::traverseHelper( + INT_TYPE nodei, + INT_TYPE parent_nodei, + FUNCTORS &functors, + LOCAL_DATA* data_for_parent) const noexcept +{ + const Node &node = myRoot[nodei]; + bool descend = functors.pre(nodei, data_for_parent); + if (!descend) + return; + LOCAL_DATA local_data[N]; + INT_TYPE s; + for (s = 0; s < N; ++s) { + const INT_TYPE node_int = node.child[s]; + if (Node::isInternal(node_int)) { + if (node_int == Node::EMPTY) { + // NOTE: Anything after this will be empty too, so we can break. + break; + } + traverseHelper(Node::getInternalNum(node_int), nodei, functors, &local_data[s]); + } + else { + functors.item(node_int, nodei, local_data[s]); + } + } + // NOTE: s is now the number of non-empty entries in this node. + functors.post(nodei, parent_nodei, data_for_parent, s, local_data); +} + +template +template +inline void BVH::traverseParallel( + INT_TYPE parallel_threshold, + FUNCTORS& functors, + LOCAL_DATA* data_for_parent) const noexcept +{ + if (!myRoot) + return; + + // NOTE: The root is always index 0. + traverseParallelHelper(0, INT_TYPE(-1), parallel_threshold, myNumNodes, functors, data_for_parent); +} +template +template +inline void BVH::traverseParallelHelper( + INT_TYPE nodei, + INT_TYPE parent_nodei, + INT_TYPE parallel_threshold, + INT_TYPE next_node_id, + FUNCTORS& functors, + LOCAL_DATA* data_for_parent) const noexcept +{ + const Node &node = myRoot[nodei]; + bool descend = functors.pre(nodei, data_for_parent); + if (!descend) + return; + + // To determine the number of nodes in a child's subtree, we take the next + // node ID minus the current child's node ID. + INT_TYPE next_nodes[N]; + INT_TYPE nnodes[N]; + INT_TYPE nchildren = N; + INT_TYPE nparallel = 0; + // s is currently unsigned, so we check s < N for bounds check. + // The s >= 0 check is in case s ever becomes signed, and should be + // automatically removed by the compiler for unsigned s. + for (INT_TYPE s = N-1; (std::is_signed::value ? (s >= 0) : (s < N)); --s) { + const INT_TYPE node_int = node.child[s]; + if (node_int == Node::EMPTY) { + --nchildren; + continue; + } + next_nodes[s] = next_node_id; + if (Node::isInternal(node_int)) { + // NOTE: This depends on BVH::initNode appending the child nodes + // in between their content, instead of all at once. + INT_TYPE child_node_id = Node::getInternalNum(node_int); + nnodes[s] = next_node_id - child_node_id; + next_node_id = child_node_id; + } + else { + nnodes[s] = 0; + } + nparallel += (nnodes[s] >= parallel_threshold); + } + + LOCAL_DATA local_data[N]; + if (nparallel >= 2) { + // Do any non-parallel ones first + if (nparallel < nchildren) { + for (INT_TYPE s = 0; s < N; ++s) { + if (nnodes[s] >= parallel_threshold) { + continue; + } + const INT_TYPE node_int = node.child[s]; + if (Node::isInternal(node_int)) { + if (node_int == Node::EMPTY) { + // NOTE: Anything after this will be empty too, so we can break. + break; + } + traverseHelper(Node::getInternalNum(node_int), nodei, functors, &local_data[s]); + } + else { + functors.item(node_int, nodei, local_data[s]); + } + } + } + // Now do the parallel ones + igl::parallel_for( + nparallel, + [this,nodei,&node,&nnodes,&next_nodes,¶llel_threshold,&functors,&local_data](int taski) + { + INT_TYPE parallel_count = 0; + // NOTE: The check for s < N is just so that the compiler can + // (hopefully) figure out that it can fully unroll the loop. + INT_TYPE s; + for (s = 0; s < N; ++s) { + if (nnodes[s] < parallel_threshold) { + continue; + } + if (parallel_count == taski) { + break; + } + ++parallel_count; + } + const INT_TYPE node_int = node.child[s]; + if (Node::isInternal(node_int)) { + UT_IGL_ASSERT_MSG_P(node_int != Node::EMPTY, "Empty entries should have been excluded above."); + traverseParallelHelper(Node::getInternalNum(node_int), nodei, parallel_threshold, next_nodes[s], functors, &local_data[s]); + } + else { + functors.item(node_int, nodei, local_data[s]); + } + }); + } + else { + // All in serial + for (INT_TYPE s = 0; s < N; ++s) { + const INT_TYPE node_int = node.child[s]; + if (Node::isInternal(node_int)) { + if (node_int == Node::EMPTY) { + // NOTE: Anything after this will be empty too, so we can break. + break; + } + traverseHelper(Node::getInternalNum(node_int), nodei, functors, &local_data[s]); + } + else { + functors.item(node_int, nodei, local_data[s]); + } + } + } + functors.post(nodei, parent_nodei, data_for_parent, nchildren, local_data); +} + +template +template +inline void BVH::traverseVector( + FUNCTORS &functors, + LOCAL_DATA* data_for_parent) const noexcept +{ + if (!myRoot) + return; + + // NOTE: The root is always index 0. + traverseVectorHelper(0, INT_TYPE(-1), functors, data_for_parent); +} +template +template +inline void BVH::traverseVectorHelper( + INT_TYPE nodei, + INT_TYPE parent_nodei, + FUNCTORS &functors, + LOCAL_DATA* data_for_parent) const noexcept +{ + const Node &node = myRoot[nodei]; + INT_TYPE descend = functors.pre(nodei, data_for_parent); + if (!descend) + return; + LOCAL_DATA local_data[N]; + INT_TYPE s; + for (s = 0; s < N; ++s) { + if ((descend>>s) & 1) { + const INT_TYPE node_int = node.child[s]; + if (Node::isInternal(node_int)) { + if (node_int == Node::EMPTY) { + // NOTE: Anything after this will be empty too, so we can break. + descend &= (INT_TYPE(1)< +template +inline void BVH::createTrivialIndices(SRC_INT_TYPE* indices, const INT_TYPE n) noexcept { + igl::parallel_for(n, [indices](INT_TYPE i) { indices[i] = i; }, 65536); +} + +template +template +inline void BVH::computeFullBoundingBox(Box& axes_minmax, const BOX_TYPE* boxes, const INT_TYPE nboxes, SRC_INT_TYPE* indices) noexcept { + if (!nboxes) { + axes_minmax.initBounds(); + return; + } + INT_TYPE ntasks = 1; + if (nboxes >= 2*4096) { + INT_TYPE nprocessors = UT_Thread::getNumProcessors(); + ntasks = (nprocessors > 1) ? SYSmin(4*nprocessors, nboxes/4096) : 1; + } + if (ntasks == 1) { + Box box; + if (indices) { + box.initBounds(boxes[indices[0]]); + for (INT_TYPE i = 1; i < nboxes; ++i) { + box.combine(boxes[indices[i]]); + } + } + else { + box.initBounds(boxes[0]); + for (INT_TYPE i = 1; i < nboxes; ++i) { + box.combine(boxes[i]); + } + } + axes_minmax = box; + } + else { + UT_SmallArray> parallel_boxes; + Box box; + igl::parallel_for( + nboxes, + [¶llel_boxes](int n){parallel_boxes.setSize(n);}, + [¶llel_boxes,indices,&boxes](int i, int t) + { + if(indices) + { + parallel_boxes[t].combine(boxes[indices[i]]); + }else + { + parallel_boxes[t].combine(boxes[i]); + } + }, + [¶llel_boxes,&box](int t) + { + if(t == 0) + { + box = parallel_boxes[0]; + }else + { + box.combine(parallel_boxes[t]); + } + }); + + axes_minmax = box; + } +} + +template +template +inline void BVH::initNode(UT_Array& nodes, Node &node, const Box& axes_minmax, const BOX_TYPE* boxes, SRC_INT_TYPE* indices, const INT_TYPE nboxes) noexcept { + if (nboxes <= N) { + // Fits in one node + for (INT_TYPE i = 0; i < nboxes; ++i) { + node.child[i] = indices[i]; + } + for (INT_TYPE i = nboxes; i < N; ++i) { + node.child[i] = Node::EMPTY; + } + return; + } + + SRC_INT_TYPE* sub_indices[N+1]; + Box sub_boxes[N]; + + if (N == 2) { + sub_indices[0] = indices; + sub_indices[2] = indices+nboxes; + split(axes_minmax, boxes, indices, nboxes, sub_indices[1], &sub_boxes[0]); + } + else { + multiSplit(axes_minmax, boxes, indices, nboxes, sub_indices, sub_boxes); + } + + // Count the number of nodes to run in parallel and fill in single items in this node + INT_TYPE nparallel = 0; + static constexpr INT_TYPE PARALLEL_THRESHOLD = 1024; + for (INT_TYPE i = 0; i < N; ++i) { + INT_TYPE sub_nboxes = sub_indices[i+1]-sub_indices[i]; + if (sub_nboxes == 1) { + node.child[i] = sub_indices[i][0]; + } + else if (sub_nboxes >= PARALLEL_THRESHOLD) { + ++nparallel; + } + } + + // NOTE: Child nodes of this node need to be placed just before the nodes in + // their corresponding subtree, in between the subtrees, because + // traverseParallel uses the difference between the child node IDs + // to determine the number of nodes in the subtree. + + // Recurse + if (nparallel >= 2) { + UT_SmallArray> parallel_nodes; + UT_SmallArray parallel_parent_nodes; + parallel_nodes.setSize(nparallel); + parallel_parent_nodes.setSize(nparallel); + igl::parallel_for( + nparallel, + [¶llel_nodes,¶llel_parent_nodes,&sub_indices,boxes,&sub_boxes](int taski) + { + // First, find which child this is + INT_TYPE counted_parallel = 0; + INT_TYPE sub_nboxes; + INT_TYPE childi; + for (childi = 0; childi < N; ++childi) { + sub_nboxes = sub_indices[childi+1]-sub_indices[childi]; + if (sub_nboxes >= PARALLEL_THRESHOLD) { + if (counted_parallel == taski) { + break; + } + ++counted_parallel; + } + } + UT_IGL_ASSERT_P(counted_parallel == taski); + + UT_Array& local_nodes = parallel_nodes[taski]; + // Preallocate an overestimate of the number of nodes needed. + // At worst, we could have only 2 children in every leaf, and + // then above that, we have a geometric series with r=1/N and a=(sub_nboxes/2)/N + // The true worst case might be a little worst than this, but + // it's probably fairly unlikely. + local_nodes.setCapacity(nodeEstimate(sub_nboxes)); + Node& parent_node = parallel_parent_nodes[taski]; + + // We'll have to fix the internal node numbers in parent_node and local_nodes later + initNode(local_nodes, parent_node, sub_boxes[childi], boxes, sub_indices[childi], sub_nboxes); + }); + + INT_TYPE counted_parallel = 0; + for (INT_TYPE i = 0; i < N; ++i) { + INT_TYPE sub_nboxes = sub_indices[i+1]-sub_indices[i]; + if (sub_nboxes != 1) { + INT_TYPE local_nodes_start = nodes.size(); + node.child[i] = Node::markInternal(local_nodes_start); + if (sub_nboxes >= PARALLEL_THRESHOLD) { + // First, adjust the root child node + Node child_node = parallel_parent_nodes[counted_parallel]; + ++local_nodes_start; + for (INT_TYPE childi = 0; childi < N; ++childi) { + INT_TYPE child_child = child_node.child[childi]; + if (Node::isInternal(child_child) && child_child != Node::EMPTY) { + child_child += local_nodes_start; + child_node.child[childi] = child_child; + } + } + + // Make space in the array for the sub-child nodes + const UT_Array& local_nodes = parallel_nodes[counted_parallel]; + ++counted_parallel; + INT_TYPE n = local_nodes.size(); + nodes.bumpCapacity(local_nodes_start + n); + nodes.setSizeNoInit(local_nodes_start + n); + nodes[local_nodes_start-1] = child_node; + } + else { + nodes.bumpCapacity(local_nodes_start + 1); + nodes.setSizeNoInit(local_nodes_start + 1); + initNode(nodes, nodes[local_nodes_start], sub_boxes[i], boxes, sub_indices[i], sub_nboxes); + } + } + } + + // Now, adjust and copy all sub-child nodes that were made in parallel + adjustParallelChildNodes(nparallel, nodes, node, parallel_nodes.array(), sub_indices); + } + else { + for (INT_TYPE i = 0; i < N; ++i) { + INT_TYPE sub_nboxes = sub_indices[i+1]-sub_indices[i]; + if (sub_nboxes != 1) { + INT_TYPE local_nodes_start = nodes.size(); + node.child[i] = Node::markInternal(local_nodes_start); + nodes.bumpCapacity(local_nodes_start + 1); + nodes.setSizeNoInit(local_nodes_start + 1); + initNode(nodes, nodes[local_nodes_start], sub_boxes[i], boxes, sub_indices[i], sub_nboxes); + } + } + } +} + +template +template +inline void BVH::initNodeReorder(UT_Array& nodes, Node &node, const Box& axes_minmax, const BOX_TYPE* boxes, SRC_INT_TYPE* indices, INT_TYPE nboxes, const INT_TYPE indices_offset, const INT_TYPE max_items_per_leaf) noexcept { + if (nboxes <= N) { + // Fits in one node + for (INT_TYPE i = 0; i < nboxes; ++i) { + node.child[i] = indices_offset+i; + } + for (INT_TYPE i = nboxes; i < N; ++i) { + node.child[i] = Node::EMPTY; + } + return; + } + + SRC_INT_TYPE* sub_indices[N+1]; + Box sub_boxes[N]; + + if (N == 2) { + sub_indices[0] = indices; + sub_indices[2] = indices+nboxes; + split(axes_minmax, boxes, indices, nboxes, sub_indices[1], &sub_boxes[0]); + } + else { + multiSplit(axes_minmax, boxes, indices, nboxes, sub_indices, sub_boxes); + } + + // Move any children with max_items_per_leaf or fewer indices before any children with more, + // for better cache coherence when we're accessing data in a corresponding array. + INT_TYPE nleaves = 0; + UT_SmallArray leaf_indices; + SRC_INT_TYPE leaf_sizes[N]; + INT_TYPE sub_nboxes0 = sub_indices[1]-sub_indices[0]; + if (sub_nboxes0 <= max_items_per_leaf) { + leaf_sizes[0] = sub_nboxes0; + for (int j = 0; j < sub_nboxes0; ++j) + leaf_indices.append(sub_indices[0][j]); + ++nleaves; + } + INT_TYPE sub_nboxes1 = sub_indices[2]-sub_indices[1]; + if (sub_nboxes1 <= max_items_per_leaf) { + leaf_sizes[nleaves] = sub_nboxes1; + for (int j = 0; j < sub_nboxes1; ++j) + leaf_indices.append(sub_indices[1][j]); + ++nleaves; + } + for (INT_TYPE i = 2; i < N; ++i) { + INT_TYPE sub_nboxes = sub_indices[i+1]-sub_indices[i]; + if (sub_nboxes <= max_items_per_leaf) { + leaf_sizes[nleaves] = sub_nboxes; + for (int j = 0; j < sub_nboxes; ++j) + leaf_indices.append(sub_indices[i][j]); + ++nleaves; + } + } + if (nleaves > 0) { + // NOTE: i < N condition is because INT_TYPE is unsigned. + // i >= 0 condition is in case INT_TYPE is changed to signed. + INT_TYPE move_distance = 0; + INT_TYPE index_move_distance = 0; + for (INT_TYPE i = N-1; (std::is_signed::value ? (i >= 0) : (i < N)); --i) { + INT_TYPE sub_nboxes = sub_indices[i+1]-sub_indices[i]; + if (sub_nboxes <= max_items_per_leaf) { + ++move_distance; + index_move_distance += sub_nboxes; + } + else if (move_distance > 0) { + SRC_INT_TYPE *start_src_index = sub_indices[i]; + for (SRC_INT_TYPE *src_index = sub_indices[i+1]-1; src_index >= start_src_index; --src_index) { + src_index[index_move_distance] = src_index[0]; + } + sub_indices[i+move_distance] = sub_indices[i]+index_move_distance; + } + } + index_move_distance = 0; + for (INT_TYPE i = 0; i < nleaves; ++i) { + INT_TYPE sub_nboxes = leaf_sizes[i]; + sub_indices[i] = indices+index_move_distance; + for (int j = 0; j < sub_nboxes; ++j) + indices[index_move_distance+j] = leaf_indices[index_move_distance+j]; + index_move_distance += sub_nboxes; + } + } + + // Count the number of nodes to run in parallel and fill in single items in this node + INT_TYPE nparallel = 0; + static constexpr INT_TYPE PARALLEL_THRESHOLD = 1024; + for (INT_TYPE i = 0; i < N; ++i) { + INT_TYPE sub_nboxes = sub_indices[i+1]-sub_indices[i]; + if (sub_nboxes <= max_items_per_leaf) { + node.child[i] = indices_offset+(sub_indices[i]-sub_indices[0]); + } + else if (sub_nboxes >= PARALLEL_THRESHOLD) { + ++nparallel; + } + } + + // NOTE: Child nodes of this node need to be placed just before the nodes in + // their corresponding subtree, in between the subtrees, because + // traverseParallel uses the difference between the child node IDs + // to determine the number of nodes in the subtree. + + // Recurse + if (nparallel >= 2 && false) { + assert(false && "Not implemented; should never get here"); + exit(1); + // // Do the parallel ones first, so that they can be inserted in the right place. + // // Although the choice may seem somewhat arbitrary, we need the results to be + // // identical whether we choose to parallelize or not, and in case we change the + // // threshold later. + // UT_SmallArray,4*sizeof(UT_Array)> parallel_nodes; + // parallel_nodes.setSize(nparallel); + // UT_SmallArray parallel_parent_nodes; + // parallel_parent_nodes.setSize(nparallel); + // UTparallelFor(UT_BlockedRange(0,nparallel), [¶llel_nodes,¶llel_parent_nodes,&sub_indices,boxes,&sub_boxes,indices_offset,max_items_per_leaf](const UT_BlockedRange& r) { + // for (INT_TYPE taski = r.begin(), end = r.end(); taski < end; ++taski) { + // // First, find which child this is + // INT_TYPE counted_parallel = 0; + // INT_TYPE sub_nboxes; + // INT_TYPE childi; + // for (childi = 0; childi < N; ++childi) { + // sub_nboxes = sub_indices[childi+1]-sub_indices[childi]; + // if (sub_nboxes >= PARALLEL_THRESHOLD) { + // if (counted_parallel == taski) { + // break; + // } + // ++counted_parallel; + // } + // } + // UT_IGL_ASSERT_P(counted_parallel == taski); + + // UT_Array& local_nodes = parallel_nodes[taski]; + // // Preallocate an overestimate of the number of nodes needed. + // // At worst, we could have only 2 children in every leaf, and + // // then above that, we have a geometric series with r=1/N and a=(sub_nboxes/2)/N + // // The true worst case might be a little worst than this, but + // // it's probably fairly unlikely. + // local_nodes.setCapacity(nodeEstimate(sub_nboxes)); + // Node& parent_node = parallel_parent_nodes[taski]; + + // // We'll have to fix the internal node numbers in parent_node and local_nodes later + // initNodeReorder(local_nodes, parent_node, sub_boxes[childi], boxes, sub_indices[childi], sub_nboxes, + // indices_offset+(sub_indices[childi]-sub_indices[0]), max_items_per_leaf); + // } + // }, 0, 1); + + // INT_TYPE counted_parallel = 0; + // for (INT_TYPE i = 0; i < N; ++i) { + // INT_TYPE sub_nboxes = sub_indices[i+1]-sub_indices[i]; + // if (sub_nboxes > max_items_per_leaf) { + // INT_TYPE local_nodes_start = nodes.size(); + // node.child[i] = Node::markInternal(local_nodes_start); + // if (sub_nboxes >= PARALLEL_THRESHOLD) { + // // First, adjust the root child node + // Node child_node = parallel_parent_nodes[counted_parallel]; + // ++local_nodes_start; + // for (INT_TYPE childi = 0; childi < N; ++childi) { + // INT_TYPE child_child = child_node.child[childi]; + // if (Node::isInternal(child_child) && child_child != Node::EMPTY) { + // child_child += local_nodes_start; + // child_node.child[childi] = child_child; + // } + // } + + // // Make space in the array for the sub-child nodes + // const UT_Array& local_nodes = parallel_nodes[counted_parallel]; + // ++counted_parallel; + // INT_TYPE n = local_nodes.size(); + // nodes.bumpCapacity(local_nodes_start + n); + // nodes.setSizeNoInit(local_nodes_start + n); + // nodes[local_nodes_start-1] = child_node; + // } + // else { + // nodes.bumpCapacity(local_nodes_start + 1); + // nodes.setSizeNoInit(local_nodes_start + 1); + // initNodeReorder(nodes, nodes[local_nodes_start], sub_boxes[i], boxes, sub_indices[i], sub_nboxes, + // indices_offset+(sub_indices[i]-sub_indices[0]), max_items_per_leaf); + // } + // } + // } + + // // Now, adjust and copy all sub-child nodes that were made in parallel + // adjustParallelChildNodes(nparallel, nodes, node, parallel_nodes.array(), sub_indices); + } + else { + for (INT_TYPE i = 0; i < N; ++i) { + INT_TYPE sub_nboxes = sub_indices[i+1]-sub_indices[i]; + if (sub_nboxes > max_items_per_leaf) { + INT_TYPE local_nodes_start = nodes.size(); + node.child[i] = Node::markInternal(local_nodes_start); + nodes.bumpCapacity(local_nodes_start + 1); + nodes.setSizeNoInit(local_nodes_start + 1); + initNodeReorder(nodes, nodes[local_nodes_start], sub_boxes[i], boxes, sub_indices[i], sub_nboxes, + indices_offset+(sub_indices[i]-sub_indices[0]), max_items_per_leaf); + } + } + } +} + +template +template +inline void BVH::multiSplit(const Box& axes_minmax, const BOX_TYPE* boxes, SRC_INT_TYPE* indices, INT_TYPE nboxes, SRC_INT_TYPE* sub_indices[N+1], Box sub_boxes[N]) noexcept { + sub_indices[0] = indices; + sub_indices[2] = indices+nboxes; + split(axes_minmax, boxes, indices, nboxes, sub_indices[1], &sub_boxes[0]); + + if (N == 2) { + return; + } + + if (H == BVH_Heuristic::MEDIAN_MAX_AXIS) { + SRC_INT_TYPE* sub_indices_startend[2*N]; + Box sub_boxes_unsorted[N]; + sub_boxes_unsorted[0] = sub_boxes[0]; + sub_boxes_unsorted[1] = sub_boxes[1]; + sub_indices_startend[0] = sub_indices[0]; + sub_indices_startend[1] = sub_indices[1]; + sub_indices_startend[2] = sub_indices[1]; + sub_indices_startend[3] = sub_indices[2]; + for (INT_TYPE nsub = 2; nsub < N; ++nsub) { + SRC_INT_TYPE* selected_start = sub_indices_startend[0]; + SRC_INT_TYPE* selected_end = sub_indices_startend[1]; + Box sub_box = sub_boxes_unsorted[0]; + + // Shift results back. + for (INT_TYPE i = 0; i < nsub-1; ++i) { + sub_indices_startend[2*i ] = sub_indices_startend[2*i+2]; + sub_indices_startend[2*i+1] = sub_indices_startend[2*i+3]; + } + for (INT_TYPE i = 0; i < nsub-1; ++i) { + sub_boxes_unsorted[i] = sub_boxes_unsorted[i-1]; + } + + // Do the split + split(sub_box, boxes, selected_start, selected_end-selected_start, sub_indices_startend[2*nsub-1], &sub_boxes_unsorted[nsub]); + sub_indices_startend[2*nsub-2] = selected_start; + sub_indices_startend[2*nsub] = sub_indices_startend[2*nsub-1]; + sub_indices_startend[2*nsub+1] = selected_end; + + // Sort pointers so that they're in the correct order + sub_indices[N] = indices+nboxes; + for (INT_TYPE i = 0; i < N; ++i) { + SRC_INT_TYPE* prev_pointer = (i != 0) ? sub_indices[i-1] : nullptr; + SRC_INT_TYPE* min_pointer = nullptr; + Box box; + for (INT_TYPE j = 0; j < N; ++j) { + SRC_INT_TYPE* cur_pointer = sub_indices_startend[2*j]; + if ((cur_pointer > prev_pointer) && (!min_pointer || (cur_pointer < min_pointer))) { + min_pointer = cur_pointer; + box = sub_boxes_unsorted[j]; + } + } + UT_IGL_ASSERT_P(min_pointer); + sub_indices[i] = min_pointer; + sub_boxes[i] = box; + } + } + } + else { + T sub_box_areas[N]; + sub_box_areas[0] = unweightedHeuristic(sub_boxes[0]); + sub_box_areas[1] = unweightedHeuristic(sub_boxes[1]); + for (INT_TYPE nsub = 2; nsub < N; ++nsub) { + // Choose which one to split + INT_TYPE split_choice = INT_TYPE(-1); + T max_heuristic; + for (INT_TYPE i = 0; i < nsub; ++i) { + const INT_TYPE index_count = (sub_indices[i+1]-sub_indices[i]); + if (index_count > 1) { + const T heuristic = sub_box_areas[i]*index_count; + if (split_choice == INT_TYPE(-1) || heuristic > max_heuristic) { + split_choice = i; + max_heuristic = heuristic; + } + } + } + UT_IGL_ASSERT_MSG_P(split_choice != INT_TYPE(-1), "There should always be at least one that can be split!"); + + SRC_INT_TYPE* selected_start = sub_indices[split_choice]; + SRC_INT_TYPE* selected_end = sub_indices[split_choice+1]; + + // Shift results over; we can skip the one we selected. + for (INT_TYPE i = nsub; i > split_choice; --i) { + sub_indices[i+1] = sub_indices[i]; + } + for (INT_TYPE i = nsub-1; i > split_choice; --i) { + sub_boxes[i+1] = sub_boxes[i]; + } + for (INT_TYPE i = nsub-1; i > split_choice; --i) { + sub_box_areas[i+1] = sub_box_areas[i]; + } + + // Do the split + split(sub_boxes[split_choice], boxes, selected_start, selected_end-selected_start, sub_indices[split_choice+1], &sub_boxes[split_choice]); + sub_box_areas[split_choice] = unweightedHeuristic(sub_boxes[split_choice]); + sub_box_areas[split_choice+1] = unweightedHeuristic(sub_boxes[split_choice+1]); + } + } +} + +template +template +inline void BVH::split(const Box& axes_minmax, const BOX_TYPE* boxes, SRC_INT_TYPE* indices, INT_TYPE nboxes, SRC_INT_TYPE*& split_indices, Box* split_boxes) noexcept { + if (nboxes == 2) { + split_boxes[0].initBounds(boxes[indices[0]]); + split_boxes[1].initBounds(boxes[indices[1]]); + split_indices = indices+1; + return; + } + UT_IGL_ASSERT_MSG_P(nboxes > 2, "Cases with less than 3 boxes should have already been handled!"); + + if (H == BVH_Heuristic::MEDIAN_MAX_AXIS) { + UT_IGL_ASSERT_MSG(0, "FIXME: Implement this!!!"); + } + + constexpr INT_TYPE SMALL_LIMIT = 6; + if (nboxes <= SMALL_LIMIT) { + // Special case for a small number of boxes: check all (2^(n-1))-1 partitions. + // Without loss of generality, we assume that box 0 is in partition 0, + // and that not all boxes are in partition 0. + Box local_boxes[SMALL_LIMIT]; + for (INT_TYPE box = 0; box < nboxes; ++box) { + local_boxes[box].initBounds(boxes[indices[box]]); + //printf("Box %u: (%f-%f)x(%f-%f)x(%f-%f)\n", uint(box), local_boxes[box].vals[0][0], local_boxes[box].vals[0][1], local_boxes[box].vals[1][0], local_boxes[box].vals[1][1], local_boxes[box].vals[2][0], local_boxes[box].vals[2][1]); + } + const INT_TYPE partition_limit = (INT_TYPE(1)<<(nboxes-1)); + INT_TYPE best_partition = INT_TYPE(-1); + T best_heuristic; + for (INT_TYPE partition_bits = 1; partition_bits < partition_limit; ++partition_bits) { + Box sub_boxes[2]; + sub_boxes[0] = local_boxes[0]; + sub_boxes[1].initBounds(); + INT_TYPE sub_counts[2] = {1,0}; + for (INT_TYPE bit = 0; bit < nboxes-1; ++bit) { + INT_TYPE dest = (partition_bits>>bit)&1; + sub_boxes[dest].combine(local_boxes[bit+1]); + ++sub_counts[dest]; + } + //printf("Partition bits %u: sub_box[0]: (%f-%f)x(%f-%f)x(%f-%f)\n", uint(partition_bits), sub_boxes[0].vals[0][0], sub_boxes[0].vals[0][1], sub_boxes[0].vals[1][0], sub_boxes[0].vals[1][1], sub_boxes[0].vals[2][0], sub_boxes[0].vals[2][1]); + //printf("Partition bits %u: sub_box[1]: (%f-%f)x(%f-%f)x(%f-%f)\n", uint(partition_bits), sub_boxes[1].vals[0][0], sub_boxes[1].vals[0][1], sub_boxes[1].vals[1][0], sub_boxes[1].vals[1][1], sub_boxes[1].vals[2][0], sub_boxes[1].vals[2][1]); + const T heuristic = + unweightedHeuristic(sub_boxes[0])*sub_counts[0] + + unweightedHeuristic(sub_boxes[1])*sub_counts[1]; + //printf("Partition bits %u: heuristic = %f (= %f*%u + %f*%u)\n",uint(partition_bits),heuristic, unweightedHeuristic(sub_boxes[0]), uint(sub_counts[0]), unweightedHeuristic(sub_boxes[1]), uint(sub_counts[1])); + if (best_partition == INT_TYPE(-1) || heuristic < best_heuristic) { + //printf(" New best\n"); + best_partition = partition_bits; + best_heuristic = heuristic; + split_boxes[0] = sub_boxes[0]; + split_boxes[1] = sub_boxes[1]; + } + } + +#if 0 // This isn't actually necessary with the current design, because I changed how the number of subtree nodes is determined. + // If best_partition is partition_limit-1, there's only 1 box + // in partition 0. We should instead put this in partition 1, + // so that we can help always have the internal node indices first + // in each node. That gets used to (fairly) quickly determine + // the number of nodes in a sub-tree. + if (best_partition == partition_limit - 1) { + // Put the first index last. + SRC_INT_TYPE last_index = indices[0]; + SRC_INT_TYPE* dest_indices = indices; + SRC_INT_TYPE* local_split_indices = indices + nboxes-1; + for (; dest_indices != local_split_indices; ++dest_indices) { + dest_indices[0] = dest_indices[1]; + } + *local_split_indices = last_index; + split_indices = local_split_indices; + + // Swap the boxes + const Box temp_box = sub_boxes[0]; + sub_boxes[0] = sub_boxes[1]; + sub_boxes[1] = temp_box; + return; + } +#endif + + // Reorder the indices. + // NOTE: Index 0 is always in partition 0, so can stay put. + SRC_INT_TYPE local_indices[SMALL_LIMIT-1]; + for (INT_TYPE box = 0; box < nboxes-1; ++box) { + local_indices[box] = indices[box+1]; + } + SRC_INT_TYPE* dest_indices = indices+1; + SRC_INT_TYPE* src_indices = local_indices; + // Copy partition 0 + for (INT_TYPE bit = 0; bit < nboxes-1; ++bit, ++src_indices) { + if (!((best_partition>>bit)&1)) { + //printf("Copying %u into partition 0\n",uint(*src_indices)); + *dest_indices = *src_indices; + ++dest_indices; + } + } + split_indices = dest_indices; + // Copy partition 1 + src_indices = local_indices; + for (INT_TYPE bit = 0; bit < nboxes-1; ++bit, ++src_indices) { + if ((best_partition>>bit)&1) { + //printf("Copying %u into partition 1\n",uint(*src_indices)); + *dest_indices = *src_indices; + ++dest_indices; + } + } + return; + } + + uint max_axis = 0; + T max_axis_length = axes_minmax.vals[0][1] - axes_minmax.vals[0][0]; + for (uint axis = 1; axis < NAXES; ++axis) { + const T axis_length = axes_minmax.vals[axis][1] - axes_minmax.vals[axis][0]; + if (axis_length > max_axis_length) { + max_axis = axis; + max_axis_length = axis_length; + } + } + + if (!(max_axis_length > T(0))) { + // All boxes are a single point or NaN. + // Pick an arbitrary split point. + split_indices = indices + nboxes/2; + split_boxes[0] = axes_minmax; + split_boxes[1] = axes_minmax; + return; + } + + const INT_TYPE axis = max_axis; + + constexpr INT_TYPE MID_LIMIT = 2*NSPANS; + if (nboxes <= MID_LIMIT) { + // Sort along axis, and try all possible splits. + +#if 1 + // First, compute midpoints + T midpointsx2[MID_LIMIT]; + for (INT_TYPE i = 0; i < nboxes; ++i) { + midpointsx2[i] = utBoxCenter(boxes[indices[i]], axis); + } + SRC_INT_TYPE local_indices[MID_LIMIT]; + for (INT_TYPE i = 0; i < nboxes; ++i) { + local_indices[i] = i; + } + + const INT_TYPE chunk_starts[5] = {0, nboxes/4, nboxes/2, INT_TYPE((3*uint64(nboxes))/4), nboxes}; + + // For sorting, insertion sort 4 chunks and merge them + for (INT_TYPE chunk = 0; chunk < 4; ++chunk) { + const INT_TYPE start = chunk_starts[chunk]; + const INT_TYPE end = chunk_starts[chunk+1]; + for (INT_TYPE i = start+1; i < end; ++i) { + SRC_INT_TYPE indexi = local_indices[i]; + T vi = midpointsx2[indexi]; + for (INT_TYPE j = start; j < i; ++j) { + SRC_INT_TYPE indexj = local_indices[j]; + T vj = midpointsx2[indexj]; + if (vi < vj) { + do { + local_indices[j] = indexi; + indexi = indexj; + ++j; + if (j == i) { + local_indices[j] = indexi; + break; + } + indexj = local_indices[j]; + } while (true); + break; + } + } + } + } + // Merge chunks into another buffer + SRC_INT_TYPE local_indices_temp[MID_LIMIT]; + std::merge(local_indices, local_indices+chunk_starts[1], + local_indices+chunk_starts[1], local_indices+chunk_starts[2], + local_indices_temp, [&midpointsx2](const SRC_INT_TYPE a, const SRC_INT_TYPE b)->bool { + return midpointsx2[a] < midpointsx2[b]; + }); + std::merge(local_indices+chunk_starts[2], local_indices+chunk_starts[3], + local_indices+chunk_starts[3], local_indices+chunk_starts[4], + local_indices_temp+chunk_starts[2], [&midpointsx2](const SRC_INT_TYPE a, const SRC_INT_TYPE b)->bool { + return midpointsx2[a] < midpointsx2[b]; + }); + std::merge(local_indices_temp, local_indices_temp+chunk_starts[2], + local_indices_temp+chunk_starts[2], local_indices_temp+chunk_starts[4], + local_indices, [&midpointsx2](const SRC_INT_TYPE a, const SRC_INT_TYPE b)->bool { + return midpointsx2[a] < midpointsx2[b]; + }); + + // Translate local_indices into indices + for (INT_TYPE i = 0; i < nboxes; ++i) { + local_indices[i] = indices[local_indices[i]]; + } + // Copy back + for (INT_TYPE i = 0; i < nboxes; ++i) { + indices[i] = local_indices[i]; + } +#else + std::stable_sort(indices, indices+nboxes, [boxes,max_axis](SRC_INT_TYPE a, SRC_INT_TYPE b)->bool { + return utBoxCenter(boxes[a], max_axis) < utBoxCenter(boxes[b], max_axis); + }); +#endif + + // Accumulate boxes + Box left_boxes[MID_LIMIT-1]; + Box right_boxes[MID_LIMIT-1]; + const INT_TYPE nsplits = nboxes-1; + Box box_accumulator(boxes[local_indices[0]]); + left_boxes[0] = box_accumulator; + for (INT_TYPE i = 1; i < nsplits; ++i) { + box_accumulator.combine(boxes[local_indices[i]]); + left_boxes[i] = box_accumulator; + } + box_accumulator.initBounds(boxes[local_indices[nsplits-1]]); + right_boxes[nsplits-1] = box_accumulator; + for (INT_TYPE i = nsplits-1; i > 0; --i) { + box_accumulator.combine(boxes[local_indices[i]]); + right_boxes[i-1] = box_accumulator; + } + + INT_TYPE best_split = 0; + T best_local_heuristic = + unweightedHeuristic(left_boxes[0]) + + unweightedHeuristic(right_boxes[0])*(nboxes-1); + for (INT_TYPE split = 1; split < nsplits; ++split) { + const T heuristic = + unweightedHeuristic(left_boxes[split])*(split+1) + + unweightedHeuristic(right_boxes[split])*(nboxes-(split+1)); + if (heuristic < best_local_heuristic) { + best_split = split; + best_local_heuristic = heuristic; + } + } + split_indices = indices+best_split+1; + split_boxes[0] = left_boxes[best_split]; + split_boxes[1] = right_boxes[best_split]; + return; + } + + const T axis_min = axes_minmax.vals[max_axis][0]; + const T axis_length = max_axis_length; + Box span_boxes[NSPANS]; + for (INT_TYPE i = 0; i < NSPANS; ++i) { + span_boxes[i].initBounds(); + } + INT_TYPE span_counts[NSPANS]; + for (INT_TYPE i = 0; i < NSPANS; ++i) { + span_counts[i] = 0; + } + + const T axis_min_x2 = ut_BoxCentre::scale*axis_min; + // NOTE: Factor of 0.5 is factored out of the average when using the average value to determine the span that a box lies in. + const T axis_index_scale = (T(1.0/ut_BoxCentre::scale)*NSPANS)/axis_length; + constexpr INT_TYPE BOX_SPANS_PARALLEL_THRESHOLD = 2048; + INT_TYPE ntasks = 1; + if (nboxes >= BOX_SPANS_PARALLEL_THRESHOLD) { + INT_TYPE nprocessors = UT_Thread::getNumProcessors(); + ntasks = (nprocessors > 1) ? SYSmin(4*nprocessors, nboxes/(BOX_SPANS_PARALLEL_THRESHOLD/2)) : 1; + } + if (ntasks == 1) { + for (INT_TYPE indexi = 0; indexi < nboxes; ++indexi) { + const auto& box = boxes[indices[indexi]]; + const T sum = utBoxCenter(box, axis); + const uint span_index = SYSclamp(int((sum-axis_min_x2)*axis_index_scale), int(0), int(NSPANS-1)); + ++span_counts[span_index]; + Box& span_box = span_boxes[span_index]; + span_box.combine(box); + } + } + else { + UT_SmallArray> parallel_boxes; + UT_SmallArray parallel_counts; + igl::parallel_for( + nboxes, + [¶llel_boxes,¶llel_counts](int n) + { + parallel_boxes.setSize( NSPANS*n); + parallel_counts.setSize(NSPANS*n); + for(int t = 0;t& span_box = parallel_boxes[t*NSPANS+span_index]; + span_box.combine(box); + }, + [¶llel_boxes,¶llel_counts,&span_boxes,&span_counts](int t) + { + for(int i = 0;i left_boxes[NSPLITS]; + // Spans 1 to NSPANS-1 + Box right_boxes[NSPLITS]; + + // Accumulate boxes + Box box_accumulator = span_boxes[0]; + left_boxes[0] = box_accumulator; + for (INT_TYPE i = 1; i < NSPLITS; ++i) { + box_accumulator.combine(span_boxes[i]); + left_boxes[i] = box_accumulator; + } + box_accumulator = span_boxes[NSPANS-1]; + right_boxes[NSPLITS-1] = box_accumulator; + for (INT_TYPE i = NSPLITS-1; i > 0; --i) { + box_accumulator.combine(span_boxes[i]); + right_boxes[i-1] = box_accumulator; + } + + INT_TYPE left_counts[NSPLITS]; + + // Accumulate counts + INT_TYPE count_accumulator = span_counts[0]; + left_counts[0] = count_accumulator; + for (INT_TYPE spliti = 1; spliti < NSPLITS; ++spliti) { + count_accumulator += span_counts[spliti]; + left_counts[spliti] = count_accumulator; + } + + // Check which split is optimal, making sure that at least 1/MIN_FRACTION of all boxes are on each side. + const INT_TYPE min_count = nboxes/MIN_FRACTION; + UT_IGL_ASSERT_MSG_P(min_count > 0, "MID_LIMIT above should have been large enough that nboxes would be > MIN_FRACTION"); + const INT_TYPE max_count = ((MIN_FRACTION-1)*uint64(nboxes))/MIN_FRACTION; + UT_IGL_ASSERT_MSG_P(max_count < nboxes, "I'm not sure how this could happen mathematically, but it needs to be checked."); + T smallest_heuristic = std::numeric_limits::infinity(); + INT_TYPE split_index = -1; + for (INT_TYPE spliti = 0; spliti < NSPLITS; ++spliti) { + const INT_TYPE left_count = left_counts[spliti]; + if (left_count < min_count || left_count > max_count) { + continue; + } + const INT_TYPE right_count = nboxes-left_count; + const T heuristic = + left_count*unweightedHeuristic(left_boxes[spliti]) + + right_count*unweightedHeuristic(right_boxes[spliti]); + if (heuristic < smallest_heuristic) { + smallest_heuristic = heuristic; + split_index = spliti; + } + } + + SRC_INT_TYPE*const indices_end = indices+nboxes; + + if (split_index == -1) { + // No split was anywhere close to balanced, so we fall back to searching for one. + + // First, find the span containing the "balance" point, namely where left_counts goes from + // being less than min_count to more than max_count. + // If that's span 0, use max_count as the ordered index to select, + // if it's span NSPANS-1, use min_count as the ordered index to select, + // else use nboxes/2 as the ordered index to select. + //T min_pivotx2 = -std::numeric_limits::infinity(); + //T max_pivotx2 = std::numeric_limits::infinity(); + SRC_INT_TYPE* nth_index; + if (left_counts[0] > max_count) { + // Search for max_count ordered index + nth_index = indices+max_count; + //max_pivotx2 = max_axis_min_x2 + max_axis_length/(NSPANS/ut_BoxCentre::scale); + } + else if (left_counts[NSPLITS-1] < min_count) { + // Search for min_count ordered index + nth_index = indices+min_count; + //min_pivotx2 = max_axis_min_x2 + max_axis_length - max_axis_length/(NSPANS/ut_BoxCentre::scale); + } + else { + // Search for nboxes/2 ordered index + nth_index = indices+nboxes/2; + //for (INT_TYPE spliti = 1; spliti < NSPLITS; ++spliti) { + // // The second condition should be redundant, but is just in case. + // if (left_counts[spliti] > max_count || spliti == NSPLITS-1) { + // min_pivotx2 = max_axis_min_x2 + spliti*max_axis_length/(NSPANS/ut_BoxCentre::scale); + // max_pivotx2 = max_axis_min_x2 + (spliti+1)*max_axis_length/(NSPANS/ut_BoxCentre::scale); + // break; + // } + //} + } + nthElement(boxes,indices,indices+nboxes,max_axis,nth_index);//,min_pivotx2,max_pivotx2); + + split_indices = nth_index; + Box left_box(boxes[indices[0]]); + for (SRC_INT_TYPE* left_indices = indices+1; left_indices < nth_index; ++left_indices) { + left_box.combine(boxes[*left_indices]); + } + Box right_box(boxes[nth_index[0]]); + for (SRC_INT_TYPE* right_indices = nth_index+1; right_indices < indices_end; ++right_indices) { + right_box.combine(boxes[*right_indices]); + } + split_boxes[0] = left_box; + split_boxes[1] = right_box; + } + else { + const T pivotx2 = axis_min_x2 + (split_index+1)*axis_length/(NSPANS/ut_BoxCentre::scale); + SRC_INT_TYPE* ppivot_start; + SRC_INT_TYPE* ppivot_end; + partitionByCentre(boxes,indices,indices+nboxes,max_axis,pivotx2,ppivot_start,ppivot_end); + + split_indices = indices + left_counts[split_index]; + + // Ignoring roundoff error, we would have + // split_indices >= ppivot_start && split_indices <= ppivot_end, + // but it may not always be in practice. + if (split_indices >= ppivot_start && split_indices <= ppivot_end) { + split_boxes[0] = left_boxes[split_index]; + split_boxes[1] = right_boxes[split_index]; + return; + } + + // Roundoff error changed the split, so we need to recompute the boxes. + if (split_indices < ppivot_start) { + split_indices = ppivot_start; + } + else {//(split_indices > ppivot_end) + split_indices = ppivot_end; + } + + // Emergency checks, just in case + if (split_indices == indices) { + ++split_indices; + } + else if (split_indices == indices_end) { + --split_indices; + } + + Box left_box(boxes[indices[0]]); + for (SRC_INT_TYPE* left_indices = indices+1; left_indices < split_indices; ++left_indices) { + left_box.combine(boxes[*left_indices]); + } + Box right_box(boxes[split_indices[0]]); + for (SRC_INT_TYPE* right_indices = split_indices+1; right_indices < indices_end; ++right_indices) { + right_box.combine(boxes[*right_indices]); + } + split_boxes[0] = left_box; + split_boxes[1] = right_box; + } +} + +template +template +inline void BVH::adjustParallelChildNodes(INT_TYPE nparallel, UT_Array& nodes, Node& node, UT_Array* parallel_nodes, SRC_INT_TYPE* sub_indices) noexcept +{ + // Alec: No need to parallelize this... + //UTparallelFor(UT_BlockedRange(0,nparallel), [&node,&nodes,¶llel_nodes,&sub_indices](const UT_BlockedRange& r) { + INT_TYPE counted_parallel = 0; + INT_TYPE childi = 0; + for(int taski = 0;taski < nparallel; taski++) + { + //for (INT_TYPE taski = r.begin(), end = r.end(); taski < end; ++taski) { + // First, find which child this is + INT_TYPE sub_nboxes; + for (; childi < N; ++childi) { + sub_nboxes = sub_indices[childi+1]-sub_indices[childi]; + if (sub_nboxes >= PARALLEL_THRESHOLD) { + if (counted_parallel == taski) { + break; + } + ++counted_parallel; + } + } + UT_IGL_ASSERT_P(counted_parallel == taski); + + const UT_Array& local_nodes = parallel_nodes[counted_parallel]; + INT_TYPE n = local_nodes.size(); + INT_TYPE local_nodes_start = Node::getInternalNum(node.child[childi])+1; + ++counted_parallel; + ++childi; + + for (INT_TYPE j = 0; j < n; ++j) { + Node local_node = local_nodes[j]; + for (INT_TYPE childj = 0; childj < N; ++childj) { + INT_TYPE local_child = local_node.child[childj]; + if (Node::isInternal(local_child) && local_child != Node::EMPTY) { + local_child += local_nodes_start; + local_node.child[childj] = local_child; + } + } + nodes[local_nodes_start+j] = local_node; + } + } +} + +template +template +void BVH::nthElement(const BOX_TYPE* boxes, SRC_INT_TYPE* indices, const SRC_INT_TYPE* indices_end, const uint axis, SRC_INT_TYPE*const nth) noexcept {//, const T min_pivotx2, const T max_pivotx2) noexcept { + while (true) { + // Choose median of first, middle, and last as the pivot + T pivots[3] = { + utBoxCenter(boxes[indices[0]], axis), + utBoxCenter(boxes[indices[(indices_end-indices)/2]], axis), + utBoxCenter(boxes[*(indices_end-1)], axis) + }; + if (pivots[0] < pivots[1]) { + const T temp = pivots[0]; + pivots[0] = pivots[1]; + pivots[1] = temp; + } + if (pivots[0] < pivots[2]) { + const T temp = pivots[0]; + pivots[0] = pivots[2]; + pivots[2] = temp; + } + if (pivots[1] < pivots[2]) { + const T temp = pivots[1]; + pivots[1] = pivots[2]; + pivots[2] = temp; + } + T mid_pivotx2 = pivots[1]; +#if 0 + // We limit the pivot, because we know that the true value is between min and max + if (mid_pivotx2 < min_pivotx2) { + mid_pivotx2 = min_pivotx2; + } + else if (mid_pivotx2 > max_pivotx2) { + mid_pivotx2 = max_pivotx2; + } +#endif + SRC_INT_TYPE* pivot_start; + SRC_INT_TYPE* pivot_end; + partitionByCentre(boxes,indices,indices_end,axis,mid_pivotx2,pivot_start,pivot_end); + if (nth < pivot_start) { + indices_end = pivot_start; + } + else if (nth < pivot_end) { + // nth is in the middle of the pivot range, + // which is in the right place, so we're done. + return; + } + else { + indices = pivot_end; + } + if (indices_end <= indices+1) { + return; + } + } +} + +template +template +void BVH::partitionByCentre(const BOX_TYPE* boxes, SRC_INT_TYPE*const indices, const SRC_INT_TYPE*const indices_end, const uint axis, const T pivotx2, SRC_INT_TYPE*& ppivot_start, SRC_INT_TYPE*& ppivot_end) noexcept { + // TODO: Consider parallelizing this! + + // First element >= pivot + SRC_INT_TYPE* pivot_start = indices; + // First element > pivot + SRC_INT_TYPE* pivot_end = indices; + + // Loop through forward once + for (SRC_INT_TYPE* psrc_index = indices; psrc_index != indices_end; ++psrc_index) { + const T srcsum = utBoxCenter(boxes[*psrc_index], axis); + if (srcsum < pivotx2) { + if (psrc_index != pivot_start) { + if (pivot_start == pivot_end) { + // Common case: nothing equal to the pivot + const SRC_INT_TYPE temp = *psrc_index; + *psrc_index = *pivot_start; + *pivot_start = temp; + } + else { + // Less common case: at least one thing equal to the pivot + const SRC_INT_TYPE temp = *psrc_index; + *psrc_index = *pivot_end; + *pivot_end = *pivot_start; + *pivot_start = temp; + } + } + ++pivot_start; + ++pivot_end; + } + else if (srcsum == pivotx2) { + // Add to the pivot area + if (psrc_index != pivot_end) { + const SRC_INT_TYPE temp = *psrc_index; + *psrc_index = *pivot_end; + *pivot_end = temp; + } + ++pivot_end; + } + } + ppivot_start = pivot_start; + ppivot_end = pivot_end; +} + +#if 0 +template +void BVH::debugDump() const { + printf("\nNode 0: {\n"); + UT_WorkBuffer indent; + indent.append(80, ' '); + UT_Array stack; + stack.append(0); + stack.append(0); + while (!stack.isEmpty()) { + int depth = stack.size()/2; + if (indent.length() < 4*depth) { + indent.append(4, ' '); + } + INT_TYPE cur_nodei = stack[stack.size()-2]; + INT_TYPE cur_i = stack[stack.size()-1]; + if (cur_i == N) { + printf(indent.buffer()+indent.length()-(4*(depth-1))); + printf("}\n"); + stack.removeLast(); + stack.removeLast(); + continue; + } + ++stack[stack.size()-1]; + Node& cur_node = myRoot[cur_nodei]; + INT_TYPE child_nodei = cur_node.child[cur_i]; + if (Node::isInternal(child_nodei)) { + if (child_nodei == Node::EMPTY) { + printf(indent.buffer()+indent.length()-(4*(depth-1))); + printf("}\n"); + stack.removeLast(); + stack.removeLast(); + continue; + } + INT_TYPE internal_node = Node::getInternalNum(child_nodei); + printf(indent.buffer()+indent.length()-(4*depth)); + printf("Node %u: {\n", uint(internal_node)); + stack.append(internal_node); + stack.append(0); + continue; + } + else { + printf(indent.buffer()+indent.length()-(4*depth)); + printf("Tri %u\n", uint(child_nodei)); + } + } +} +#endif + +} // UT namespace +} // End HDK_Sample namespace +}} +#endif +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * Functions and structures for computing solid angles. + */ + +#pragma once + +#ifndef __HDK_UT_SolidAngle_h__ +#define __HDK_UT_SolidAngle_h__ + + + + + +#include + +namespace igl { + /// @private + namespace FastWindingNumber { +namespace HDK_Sample { + +template +using UT_Vector2T = UT_FixedVector; +template +using UT_Vector3T = UT_FixedVector; + +template +SYS_FORCE_INLINE T cross(const UT_Vector2T &v1, const UT_Vector2T &v2) +{ + return v1[0]*v2[1] - v1[1]*v2[0]; +} + +template +SYS_FORCE_INLINE +UT_Vector3T cross(const UT_Vector3T &v1, const UT_Vector3T &v2) +{ + UT_Vector3T result; + // compute the cross product: + result[0] = v1[1]*v2[2] - v1[2]*v2[1]; + result[1] = v1[2]*v2[0] - v1[0]*v2[2]; + result[2] = v1[0]*v2[1] - v1[1]*v2[0]; + return result; +} + +/// Returns the signed solid angle subtended by triangle abc +/// from query point. +/// +/// WARNING: This uses the right-handed normal convention, whereas most of +/// Houdini uses the left-handed normal convention, so either +/// negate the output, or swap b and c if you want it to be +/// positive inside and negative outside. +template +inline T UTsignedSolidAngleTri( + const UT_Vector3T &a, + const UT_Vector3T &b, + const UT_Vector3T &c, + const UT_Vector3T &query) +{ + // Make a, b, and c relative to query + UT_Vector3T qa = a-query; + UT_Vector3T qb = b-query; + UT_Vector3T qc = c-query; + + const T alength = qa.length(); + const T blength = qb.length(); + const T clength = qc.length(); + + // If any triangle vertices are coincident with query, + // query is on the surface, which we treat as no solid angle. + if (alength == 0 || blength == 0 || clength == 0) + return T(0); + + // Normalize the vectors + qa /= alength; + qb /= blength; + qc /= clength; + + // The formula on Wikipedia has roughly dot(qa,cross(qb,qc)), + // but that's unstable when qa, qb, and qc are very close, + // (e.g. if the input triangle was very far away). + // This should be equivalent, but more stable. + const T numerator = dot(qa, cross(qb-qa, qc-qa)); + + // If numerator is 0, regardless of denominator, query is on the + // surface, which we treat as no solid angle. + if (numerator == 0) + return T(0); + + const T denominator = T(1) + dot(qa,qb) + dot(qa,qc) + dot(qb,qc); + + return T(2)*SYSatan2(numerator, denominator); +} + +template +inline T UTsignedSolidAngleQuad( + const UT_Vector3T &a, + const UT_Vector3T &b, + const UT_Vector3T &c, + const UT_Vector3T &d, + const UT_Vector3T &query) +{ + // Make a, b, c, and d relative to query + UT_Vector3T v[4] = { + a-query, + b-query, + c-query, + d-query + }; + + const T lengths[4] = { + v[0].length(), + v[1].length(), + v[2].length(), + v[3].length() + }; + + // If any quad vertices are coincident with query, + // query is on the surface, which we treat as no solid angle. + // We could add the contribution from the non-planar part, + // but in the context of a mesh, we'd still miss some, like + // we do in the triangle case. + if (lengths[0] == T(0) || lengths[1] == T(0) || lengths[2] == T(0) || lengths[3] == T(0)) + return T(0); + + // Normalize the vectors + v[0] /= lengths[0]; + v[1] /= lengths[1]; + v[2] /= lengths[2]; + v[3] /= lengths[3]; + + // Compute (unnormalized, but consistently-scaled) barycentric coordinates + // for the query point inside the tetrahedron of points. + // If 0 or 4 of the coordinates are positive, (or slightly negative), the + // query is (approximately) inside, so the choice of triangulation matters. + // Otherwise, the triangulation doesn't matter. + + const UT_Vector3T diag02 = v[2]-v[0]; + const UT_Vector3T diag13 = v[3]-v[1]; + const UT_Vector3T v01 = v[1]-v[0]; + const UT_Vector3T v23 = v[3]-v[2]; + + T bary[4]; + bary[0] = dot(v[3],cross(v23,diag13)); + bary[1] = -dot(v[2],cross(v23,diag02)); + bary[2] = -dot(v[1],cross(v01,diag13)); + bary[3] = dot(v[0],cross(v01,diag02)); + + const T dot01 = dot(v[0],v[1]); + const T dot12 = dot(v[1],v[2]); + const T dot23 = dot(v[2],v[3]); + const T dot30 = dot(v[3],v[0]); + + T omega = T(0); + + // Equation of a bilinear patch in barycentric coordinates of its + // tetrahedron is x0*x2 = x1*x3. Less is one side; greater is other. + if (bary[0]*bary[2] < bary[1]*bary[3]) + { + // Split 0-2: triangles 0,1,2 and 0,2,3 + const T numerator012 = bary[3]; + const T numerator023 = bary[1]; + const T dot02 = dot(v[0],v[2]); + + // If numerator is 0, regardless of denominator, query is on the + // surface, which we treat as no solid angle. + if (numerator012 != T(0)) + { + const T denominator012 = T(1) + dot01 + dot12 + dot02; + omega = SYSatan2(numerator012, denominator012); + } + if (numerator023 != T(0)) + { + const T denominator023 = T(1) + dot02 + dot23 + dot30; + omega += SYSatan2(numerator023, denominator023); + } + } + else + { + // Split 1-3: triangles 0,1,3 and 1,2,3 + const T numerator013 = -bary[2]; + const T numerator123 = -bary[0]; + const T dot13 = dot(v[1],v[3]); + + // If numerator is 0, regardless of denominator, query is on the + // surface, which we treat as no solid angle. + if (numerator013 != T(0)) + { + const T denominator013 = T(1) + dot01 + dot13 + dot30; + omega = SYSatan2(numerator013, denominator013); + } + if (numerator123 != T(0)) + { + const T denominator123 = T(1) + dot12 + dot23 + dot13; + omega += SYSatan2(numerator123, denominator123); + } + } + return T(2)*omega; +} + +/// Class for quickly approximating signed solid angle of a large mesh +/// from many query points. This is useful for computing the +/// generalized winding number at many points. +/// +/// NOTE: This is currently only instantiated for . +template +class UT_SolidAngle +{ +public: + /// This is outlined so that we don't need to include UT_BVHImpl.h + inline UT_SolidAngle(); + /// This is outlined so that we don't need to include UT_BVHImpl.h + inline ~UT_SolidAngle(); + + /// NOTE: This does not take ownership over triangle_points or positions, + /// but does keep pointers to them, so the caller must keep them in + /// scope for the lifetime of this structure. + UT_SolidAngle( + const int ntriangles, + const int *const triangle_points, + const int npoints, + const UT_Vector3T *const positions, + const int order = 2) + : UT_SolidAngle() + { init(ntriangles, triangle_points, npoints, positions, order); } + + /// Initialize the tree and data. + /// NOTE: It is safe to call init on a UT_SolidAngle that has had init + /// called on it before, to re-initialize it. + inline void init( + const int ntriangles, + const int *const triangle_points, + const int npoints, + const UT_Vector3T *const positions, + const int order = 2); + + /// Frees myTree and myData, and clears the rest. + inline void clear(); + + /// Returns true if this is clear + bool isClear() const + { return myNTriangles == 0; } + + /// Returns an approximation of the signed solid angle of the mesh from the specified query_point + /// accuracy_scale is the value of (maxP/q) beyond which the approximation of the box will be used. + inline T computeSolidAngle(const UT_Vector3T &query_point, const T accuracy_scale = T(2.0)) const; + +private: + struct BoxData; + + static constexpr uint BVH_N = 4; + UT_BVH myTree; + int myNBoxes; + int myOrder; + std::unique_ptr myData; + int myNTriangles; + const int *myTrianglePoints; + int myNPoints; + const UT_Vector3T *myPositions; +}; + +template +inline T UTsignedAngleSegment( + const UT_Vector2T &a, + const UT_Vector2T &b, + const UT_Vector2T &query) +{ + // Make a and b relative to query + UT_Vector2T qa = a-query; + UT_Vector2T qb = b-query; + + // If any segment vertices are coincident with query, + // query is on the segment, which we treat as no angle. + if (qa.isZero() || qb.isZero()) + return T(0); + + // numerator = |qa||qb|sin(theta) + const T numerator = cross(qa, qb); + + // If numerator is 0, regardless of denominator, query is on the + // surface, which we treat as no solid angle. + if (numerator == 0) + return T(0); + + // denominator = |qa||qb|cos(theta) + const T denominator = dot(qa,qb); + + // numerator/denominator = tan(theta) + return SYSatan2(numerator, denominator); +} + +/// Class for quickly approximating signed subtended angle of a large curve +/// from many query points. This is useful for computing the +/// generalized winding number at many points. +/// +/// NOTE: This is currently only instantiated for . +template +class UT_SubtendedAngle +{ +public: + /// This is outlined so that we don't need to include UT_BVHImpl.h + inline UT_SubtendedAngle(); + /// This is outlined so that we don't need to include UT_BVHImpl.h + inline ~UT_SubtendedAngle(); + + /// NOTE: This does not take ownership over segment_points or positions, + /// but does keep pointers to them, so the caller must keep them in + /// scope for the lifetime of this structure. + UT_SubtendedAngle( + const int nsegments, + const int *const segment_points, + const int npoints, + const UT_Vector2T *const positions, + const int order = 2) + : UT_SubtendedAngle() + { init(nsegments, segment_points, npoints, positions, order); } + + /// Initialize the tree and data. + /// NOTE: It is safe to call init on a UT_SolidAngle that has had init + /// called on it before, to re-initialize it. + inline void init( + const int nsegments, + const int *const segment_points, + const int npoints, + const UT_Vector2T *const positions, + const int order = 2); + + /// Frees myTree and myData, and clears the rest. + inline void clear(); + + /// Returns true if this is clear + bool isClear() const + { return myNSegments == 0; } + + /// Returns an approximation of the signed solid angle of the mesh from the specified query_point + /// accuracy_scale is the value of (maxP/q) beyond which the approximation of the box will be used. + inline T computeAngle(const UT_Vector2T &query_point, const T accuracy_scale = T(2.0)) const; + +private: + struct BoxData; + + static constexpr uint BVH_N = 4; + UT_BVH myTree; + int myNBoxes; + int myOrder; + std::unique_ptr myData; + int myNSegments; + const int *mySegmentPoints; + int myNPoints; + const UT_Vector2T *myPositions; +}; + +} // End HDK_Sample namespace +}} +#endif +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * A wrapper function for the "free" function, used by UT_(Small)Array + */ + + + +#include + +namespace igl { + /// @private + namespace FastWindingNumber { + +// This needs to be here or else the warning suppression doesn't work because +// the templated calling code won't otherwise be compiled until after we've +// already popped the warning.state. So we just always disable this at file +// scope here. +#if defined(__GNUC__) && !defined(__clang__) + _Pragma("GCC diagnostic push") + _Pragma("GCC diagnostic ignored \"-Wfree-nonheap-object\"") +#endif +inline void ut_ArrayImplFree(void *p) +{ + free(p); +} +#if defined(__GNUC__) && !defined(__clang__) + _Pragma("GCC diagnostic pop") +#endif +} } +/* + * Copyright (c) 2018 Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * COMMENTS: + * Functions and structures for computing solid angles. + */ + + + + + + + + +#include "parallel_for.h" +#include +#include + +#define SOLID_ANGLE_TIME_PRECOMPUTE 0 + +#if SOLID_ANGLE_TIME_PRECOMPUTE +#include +#endif + +#define SOLID_ANGLE_DEBUG 0 +#if SOLID_ANGLE_DEBUG +#include +#endif + +#define TAYLOR_SERIES_ORDER 2 + +namespace igl { + /// @private + namespace FastWindingNumber { + +namespace HDK_Sample { + +template +struct UT_SolidAngle::BoxData +{ + void clear() + { + // Set everything to zero + memset(this,0,sizeof(*this)); + } + + using Type = typename std::conditional::value, v4uf, UT_FixedVector>::type; + using SType = typename std::conditional::value, v4uf, UT_FixedVector>::type; + + /// An upper bound on the squared distance from myAverageP to the farthest point in the box. + SType myMaxPDist2; + + /// Centre of mass of the mesh surface in this box + UT_FixedVector myAverageP; + + /// Unnormalized, area-weighted normal of the mesh in this box + UT_FixedVector myN; + +#if TAYLOR_SERIES_ORDER >= 1 + /// Values for Omega_1 + /// @{ + UT_FixedVector myNijDiag; // Nxx, Nyy, Nzz + Type myNxy_Nyx; // Nxy+Nyx + Type myNyz_Nzy; // Nyz+Nzy + Type myNzx_Nxz; // Nzx+Nxz + /// @} +#endif + +#if TAYLOR_SERIES_ORDER >= 2 + /// Values for Omega_2 + /// @{ + UT_FixedVector myNijkDiag; // Nxxx, Nyyy, Nzzz + Type mySumPermuteNxyz; // (Nxyz+Nxzy+Nyzx+Nyxz+Nzxy+Nzyx) = 2*(Nxyz+Nyzx+Nzxy) + Type my2Nxxy_Nyxx; // Nxxy+Nxyx+Nyxx = 2Nxxy+Nyxx + Type my2Nxxz_Nzxx; // Nxxz+Nxzx+Nzxx = 2Nxxz+Nzxx + Type my2Nyyz_Nzyy; // Nyyz+Nyzy+Nzyy = 2Nyyz+Nzyy + Type my2Nyyx_Nxyy; // Nyyx+Nyxy+Nxyy = 2Nyyx+Nxyy + Type my2Nzzx_Nxzz; // Nzzx+Nzxz+Nxzz = 2Nzzx+Nxzz + Type my2Nzzy_Nyzz; // Nzzy+Nzyz+Nyzz = 2Nzzy+Nyzz + /// @} +#endif +}; + +template +inline UT_SolidAngle::UT_SolidAngle() + : myTree() + , myNBoxes(0) + , myOrder(2) + , myData(nullptr) + , myNTriangles(0) + , myTrianglePoints(nullptr) + , myNPoints(0) + , myPositions(nullptr) +{} + +template +inline UT_SolidAngle::~UT_SolidAngle() +{ + // Default destruction works, but this needs to be outlined + // to avoid having to include UT_BVHImpl.h in the header, + // (for the UT_UniquePtr destructor.) +} + +template +inline void UT_SolidAngle::init( + const int ntriangles, + const int *const triangle_points, + const int npoints, + const UT_Vector3T *const positions, + const int order) +{ +#if SOLID_ANGLE_DEBUG + UTdebugFormat(""); + UTdebugFormat(""); + UTdebugFormat("Building BVH for {} ntriangles on {} points:", ntriangles, npoints); +#endif + myOrder = order; + myNTriangles = ntriangles; + myTrianglePoints = triangle_points; + myNPoints = npoints; + myPositions = positions; + +#if SOLID_ANGLE_TIME_PRECOMPUTE + UT_StopWatch timer; + timer.start(); +#endif + UT_SmallArray> triangle_boxes; + triangle_boxes.setSizeNoInit(ntriangles); + if (ntriangles < 16*1024) + { + const int *cur_triangle_points = triangle_points; + for (int i = 0; i < ntriangles; ++i, cur_triangle_points += 3) + { + UT::Box &box = triangle_boxes[i]; + box.initBounds(positions[cur_triangle_points[0]]); + box.enlargeBounds(positions[cur_triangle_points[1]]); + box.enlargeBounds(positions[cur_triangle_points[2]]); + } + } + else + { + igl::parallel_for(ntriangles, + [triangle_points,&triangle_boxes,positions](int i) + { + const int *cur_triangle_points = triangle_points + i*3; + UT::Box &box = triangle_boxes[i]; + box.initBounds(positions[cur_triangle_points[0]]); + box.enlargeBounds(positions[cur_triangle_points[1]]); + box.enlargeBounds(positions[cur_triangle_points[2]]); + }); + } +#if SOLID_ANGLE_TIME_PRECOMPUTE + double time = timer.stop(); + UTdebugFormat("{} s to create bounding boxes.", time); + timer.start(); +#endif + myTree.template init(triangle_boxes.array(), ntriangles); +#if SOLID_ANGLE_TIME_PRECOMPUTE + time = timer.stop(); + UTdebugFormat("{} s to initialize UT_BVH structure. {} nodes", time, myTree.getNumNodes()); +#endif + + //myTree.debugDump(); + + const int nnodes = myTree.getNumNodes(); + + myNBoxes = nnodes; + BoxData *box_data = new BoxData[nnodes]; + myData.reset(box_data); + + // Some data are only needed during initialization. + struct LocalData + { + // Bounding box + UT::Box myBox; + + // P and N are needed from each child for computing Nij. + UT_Vector3T myAverageP; + UT_Vector3T myAreaP; + UT_Vector3T myN; + + // Unsigned area is needed for computing the average position. + T myArea; + +#if TAYLOR_SERIES_ORDER >= 1 + // These are needed for computing Nijk. + UT_Vector3T myNijDiag; + T myNxy; T myNyx; + T myNyz; T myNzy; + T myNzx; T myNxz; +#endif + +#if TAYLOR_SERIES_ORDER >= 2 + UT_Vector3T myNijkDiag; // Nxxx, Nyyy, Nzzz + T mySumPermuteNxyz; // (Nxyz+Nxzy+Nyzx+Nyxz+Nzxy+Nzyx) = 2*(Nxyz+Nyzx+Nzxy) + T my2Nxxy_Nyxx; // Nxxy+Nxyx+Nyxx = 2Nxxy+Nyxx + T my2Nxxz_Nzxx; // Nxxz+Nxzx+Nzxx = 2Nxxz+Nzxx + T my2Nyyz_Nzyy; // Nyyz+Nyzy+Nzyy = 2Nyyz+Nzyy + T my2Nyyx_Nxyy; // Nyyx+Nyxy+Nxyy = 2Nyyx+Nxyy + T my2Nzzx_Nxzz; // Nzzx+Nzxz+Nxzz = 2Nzzx+Nxzz + T my2Nzzy_Nyzz; // Nzzy+Nzyz+Nyzz = 2Nzzy+Nyzz +#endif + }; + + struct PrecomputeFunctors + { + BoxData *const myBoxData; + const UT::Box *const myTriangleBoxes; + const int *const myTrianglePoints; + const UT_Vector3T *const myPositions; + const int myOrder; + + PrecomputeFunctors( + BoxData *box_data, + const UT::Box *triangle_boxes, + const int *triangle_points, + const UT_Vector3T *positions, + const int order) + : myBoxData(box_data) + , myTriangleBoxes(triangle_boxes) + , myTrianglePoints(triangle_points) + , myPositions(positions) + , myOrder(order) + {} + constexpr SYS_FORCE_INLINE bool pre(const int /*nodei*/, LocalData * /*data_for_parent*/) const + { + return true; + } + void item(const int itemi, const int /*parent_nodei*/, LocalData &data_for_parent) const + { + const UT_Vector3T *const positions = myPositions; + const int *const cur_triangle_points = myTrianglePoints + 3*itemi; + const UT_Vector3T a = positions[cur_triangle_points[0]]; + const UT_Vector3T b = positions[cur_triangle_points[1]]; + const UT_Vector3T c = positions[cur_triangle_points[2]]; + const UT_Vector3T ab = b-a; + const UT_Vector3T ac = c-a; + + const UT::Box &triangle_box = myTriangleBoxes[itemi]; + data_for_parent.myBox.initBounds(triangle_box.getMin(), triangle_box.getMax()); + + // Area-weighted normal (unnormalized) + const UT_Vector3T N = T(0.5)*cross(ab,ac); + const T area2 = N.length2(); + const T area = SYSsqrt(area2); + const UT_Vector3T P = (a+b+c)/3; + data_for_parent.myAverageP = P; + data_for_parent.myAreaP = P*area; + data_for_parent.myN = N; +#if SOLID_ANGLE_DEBUG + UTdebugFormat(""); + UTdebugFormat("Triangle {}: P = {}; N = {}; area = {}", itemi, P, N, area); + UTdebugFormat(" box = {}", data_for_parent.myBox); +#endif + + data_for_parent.myArea = area; +#if TAYLOR_SERIES_ORDER >= 1 + const int order = myOrder; + if (order < 1) + return; + + // NOTE: Due to P being at the centroid, triangles have Nij = 0 + // contributions to Nij. + data_for_parent.myNijDiag = T(0); + data_for_parent.myNxy = 0; data_for_parent.myNyx = 0; + data_for_parent.myNyz = 0; data_for_parent.myNzy = 0; + data_for_parent.myNzx = 0; data_for_parent.myNxz = 0; +#endif + +#if TAYLOR_SERIES_ORDER >= 2 + if (order < 2) + return; + + // If it's zero-length, the results are zero, so we can skip. + if (area == 0) + { + data_for_parent.myNijkDiag = T(0); + data_for_parent.mySumPermuteNxyz = 0; + data_for_parent.my2Nxxy_Nyxx = 0; + data_for_parent.my2Nxxz_Nzxx = 0; + data_for_parent.my2Nyyz_Nzyy = 0; + data_for_parent.my2Nyyx_Nxyy = 0; + data_for_parent.my2Nzzx_Nxzz = 0; + data_for_parent.my2Nzzy_Nyzz = 0; + return; + } + + // We need to use the NORMALIZED normal to multiply the integrals by. + UT_Vector3T n = N/area; + + // Figure out the order of a, b, and c in x, y, and z + // for use in computing the integrals for Nijk. + UT_Vector3T values[3] = {a, b, c}; + + int order_x[3] = {0,1,2}; + if (a[0] > b[0]) + std::swap(order_x[0],order_x[1]); + if (values[order_x[0]][0] > c[0]) + std::swap(order_x[0],order_x[2]); + if (values[order_x[1]][0] > values[order_x[2]][0]) + std::swap(order_x[1],order_x[2]); + T dx = values[order_x[2]][0] - values[order_x[0]][0]; + + int order_y[3] = {0,1,2}; + if (a[1] > b[1]) + std::swap(order_y[0],order_y[1]); + if (values[order_y[0]][1] > c[1]) + std::swap(order_y[0],order_y[2]); + if (values[order_y[1]][1] > values[order_y[2]][1]) + std::swap(order_y[1],order_y[2]); + T dy = values[order_y[2]][1] - values[order_y[0]][1]; + + int order_z[3] = {0,1,2}; + if (a[2] > b[2]) + std::swap(order_z[0],order_z[1]); + if (values[order_z[0]][2] > c[2]) + std::swap(order_z[0],order_z[2]); + if (values[order_z[1]][2] > values[order_z[2]][2]) + std::swap(order_z[1],order_z[2]); + T dz = values[order_z[2]][2] - values[order_z[0]][2]; + + auto &&compute_integrals = []( + const UT_Vector3T &a, + const UT_Vector3T &b, + const UT_Vector3T &c, + const UT_Vector3T &P, + T *integral_ii, + T *integral_ij, + T *integral_ik, + const int i) + { +#if SOLID_ANGLE_DEBUG + UTdebugFormat(" Splitting on {}; a = {}; b = {}; c = {}", char('x'+i), a, b, c); +#endif + // NOTE: a, b, and c must be in order of the i axis. + // We're splitting the triangle at the middle i coordinate. + const UT_Vector3T oab = b - a; + const UT_Vector3T oac = c - a; + const UT_Vector3T ocb = b - c; + UT_IGL_ASSERT_MSG_P(oac[i] > 0, "This should have been checked by the caller."); + const T t = oab[i]/oac[i]; + UT_IGL_ASSERT_MSG_P(t >= 0 && t <= 1, "Either sorting must have gone wrong, or there are input NaNs."); + + const int j = (i==2) ? 0 : (i+1); + const int k = (j==2) ? 0 : (j+1); + const T jdiff = t*oac[j] - oab[j]; + const T kdiff = t*oac[k] - oab[k]; + UT_Vector3T cross_a; + cross_a[0] = (jdiff*oab[k] - kdiff*oab[j]); + cross_a[1] = kdiff*oab[i]; + cross_a[2] = jdiff*oab[i]; + UT_Vector3T cross_c; + cross_c[0] = (jdiff*ocb[k] - kdiff*ocb[j]); + cross_c[1] = kdiff*ocb[i]; + cross_c[2] = jdiff*ocb[i]; + const T area_scale_a = cross_a.length(); + const T area_scale_c = cross_c.length(); + const T Pai = a[i] - P[i]; + const T Pci = c[i] - P[i]; + + // Integral over the area of the triangle of (pi^2)dA, + // by splitting the triangle into two at b, the a side + // and the c side. + const T int_ii_a = area_scale_a*(T(0.5)*Pai*Pai + T(2.0/3.0)*Pai*oab[i] + T(0.25)*oab[i]*oab[i]); + const T int_ii_c = area_scale_c*(T(0.5)*Pci*Pci + T(2.0/3.0)*Pci*ocb[i] + T(0.25)*ocb[i]*ocb[i]); + *integral_ii = int_ii_a + int_ii_c; +#if SOLID_ANGLE_DEBUG + UTdebugFormat(" integral_{}{}_a = {}; integral_{}{}_c = {}", char('x'+i), char('x'+i), int_ii_a, char('x'+i), char('x'+i), int_ii_c); +#endif + + int jk = j; + T *integral = integral_ij; + T diff = jdiff; + while (true) // This only does 2 iterations, one for j and one for k + { + if (integral) + { + T obmidj = b[jk] + T(0.5)*diff; + T oabmidj = obmidj - a[jk]; + T ocbmidj = obmidj - c[jk]; + T Paj = a[jk] - P[jk]; + T Pcj = c[jk] - P[jk]; + // Integral over the area of the triangle of (pi*pj)dA + const T int_ij_a = area_scale_a*(T(0.5)*Pai*Paj + T(1.0/3.0)*Pai*oabmidj + T(1.0/3.0)*Paj*oab[i] + T(0.25)*oab[i]*oabmidj); + const T int_ij_c = area_scale_c*(T(0.5)*Pci*Pcj + T(1.0/3.0)*Pci*ocbmidj + T(1.0/3.0)*Pcj*ocb[i] + T(0.25)*ocb[i]*ocbmidj); + *integral = int_ij_a + int_ij_c; +#if SOLID_ANGLE_DEBUG + UTdebugFormat(" integral_{}{}_a = {}; integral_{}{}_c = {}", char('x'+i), char('x'+jk), int_ij_a, char('x'+i), char('x'+jk), int_ij_c); +#endif + } + if (jk == k) + break; + jk = k; + integral = integral_ik; + diff = kdiff; + } + }; + + T integral_xx = 0; + T integral_xy = 0; + T integral_yy = 0; + T integral_yz = 0; + T integral_zz = 0; + T integral_zx = 0; + // Note that if the span of any axis is zero, the integral must be zero, + // since there's a factor of (p_i-P_i), i.e. value minus average, + // and every value must be equal to the average, giving zero. + if (dx > 0) + { + compute_integrals( + values[order_x[0]], values[order_x[1]], values[order_x[2]], P, + &integral_xx, ((dx >= dy && dy > 0) ? &integral_xy : nullptr), ((dx >= dz && dz > 0) ? &integral_zx : nullptr), 0); + } + if (dy > 0) + { + compute_integrals( + values[order_y[0]], values[order_y[1]], values[order_y[2]], P, + &integral_yy, ((dy >= dz && dz > 0) ? &integral_yz : nullptr), ((dx < dy && dx > 0) ? &integral_xy : nullptr), 1); + } + if (dz > 0) + { + compute_integrals( + values[order_z[0]], values[order_z[1]], values[order_z[2]], P, + &integral_zz, ((dx < dz && dx > 0) ? &integral_zx : nullptr), ((dy < dz && dy > 0) ? &integral_yz : nullptr), 2); + } + + UT_Vector3T Niii; + Niii[0] = integral_xx; + Niii[1] = integral_yy; + Niii[2] = integral_zz; + Niii *= n; + data_for_parent.myNijkDiag = Niii; + data_for_parent.mySumPermuteNxyz = 2*(n[0]*integral_yz + n[1]*integral_zx + n[2]*integral_xy); + T Nxxy = n[0]*integral_xy; + T Nxxz = n[0]*integral_zx; + T Nyyz = n[1]*integral_yz; + T Nyyx = n[1]*integral_xy; + T Nzzx = n[2]*integral_zx; + T Nzzy = n[2]*integral_yz; + data_for_parent.my2Nxxy_Nyxx = 2*Nxxy + n[1]*integral_xx; + data_for_parent.my2Nxxz_Nzxx = 2*Nxxz + n[2]*integral_xx; + data_for_parent.my2Nyyz_Nzyy = 2*Nyyz + n[2]*integral_yy; + data_for_parent.my2Nyyx_Nxyy = 2*Nyyx + n[0]*integral_yy; + data_for_parent.my2Nzzx_Nxzz = 2*Nzzx + n[0]*integral_zz; + data_for_parent.my2Nzzy_Nyzz = 2*Nzzy + n[1]*integral_zz; +#if SOLID_ANGLE_DEBUG + UTdebugFormat(" integral_xx = {}; yy = {}; zz = {}", integral_xx, integral_yy, integral_zz); + UTdebugFormat(" integral_xy = {}; yz = {}; zx = {}", integral_xy, integral_yz, integral_zx); +#endif +#endif + } + + void post(const int nodei, const int /*parent_nodei*/, LocalData *data_for_parent, const int nchildren, const LocalData *child_data_array) const + { + // NOTE: Although in the general case, data_for_parent may be null for the root call, + // this functor assumes that it's non-null, so the call below must pass a non-null pointer. + + BoxData ¤t_box_data = myBoxData[nodei]; + + UT_Vector3T N = child_data_array[0].myN; + ((T*)¤t_box_data.myN[0])[0] = N[0]; + ((T*)¤t_box_data.myN[1])[0] = N[1]; + ((T*)¤t_box_data.myN[2])[0] = N[2]; + UT_Vector3T areaP = child_data_array[0].myAreaP; + T area = child_data_array[0].myArea; + UT_Vector3T local_P = child_data_array[0].myAverageP; + ((T*)¤t_box_data.myAverageP[0])[0] = local_P[0]; + ((T*)¤t_box_data.myAverageP[1])[0] = local_P[1]; + ((T*)¤t_box_data.myAverageP[2])[0] = local_P[2]; + for (int i = 1; i < nchildren; ++i) + { + const UT_Vector3T local_N = child_data_array[i].myN; + N += local_N; + ((T*)¤t_box_data.myN[0])[i] = local_N[0]; + ((T*)¤t_box_data.myN[1])[i] = local_N[1]; + ((T*)¤t_box_data.myN[2])[i] = local_N[2]; + areaP += child_data_array[i].myAreaP; + area += child_data_array[i].myArea; + const UT_Vector3T local_P = child_data_array[i].myAverageP; + ((T*)¤t_box_data.myAverageP[0])[i] = local_P[0]; + ((T*)¤t_box_data.myAverageP[1])[i] = local_P[1]; + ((T*)¤t_box_data.myAverageP[2])[i] = local_P[2]; + } + for (int i = nchildren; i < BVH_N; ++i) + { + // Set to zero, just to avoid false positives for uses of uninitialized memory. + ((T*)¤t_box_data.myN[0])[i] = 0; + ((T*)¤t_box_data.myN[1])[i] = 0; + ((T*)¤t_box_data.myN[2])[i] = 0; + ((T*)¤t_box_data.myAverageP[0])[i] = 0; + ((T*)¤t_box_data.myAverageP[1])[i] = 0; + ((T*)¤t_box_data.myAverageP[2])[i] = 0; + } + data_for_parent->myN = N; + data_for_parent->myAreaP = areaP; + data_for_parent->myArea = area; + + UT::Box box(child_data_array[0].myBox); + for (int i = 1; i < nchildren; ++i) + box.enlargeBounds(child_data_array[i].myBox); + + // Normalize P + UT_Vector3T averageP; + if (area > 0) + averageP = areaP/area; + else + averageP = T(0.5)*(box.getMin() + box.getMax()); + data_for_parent->myAverageP = averageP; + + data_for_parent->myBox = box; + + for (int i = 0; i < nchildren; ++i) + { + const UT::Box &local_box(child_data_array[i].myBox); + const UT_Vector3T &local_P = child_data_array[i].myAverageP; + const UT_Vector3T maxPDiff = SYSmax(local_P-UT_Vector3T(local_box.getMin()), UT_Vector3T(local_box.getMax())-local_P); + ((T*)¤t_box_data.myMaxPDist2)[i] = maxPDiff.length2(); + } + for (int i = nchildren; i < BVH_N; ++i) + { + // This child is non-existent. If we set myMaxPDist2 to infinity, it will never + // use the approximation, and the traverseVector function can check for EMPTY. + ((T*)¤t_box_data.myMaxPDist2)[i] = std::numeric_limits::infinity(); + } + +#if TAYLOR_SERIES_ORDER >= 1 + const int order = myOrder; + if (order >= 1) + { + // We now have the current box's P, so we can adjust Nij and Nijk + data_for_parent->myNijDiag = child_data_array[0].myNijDiag; + data_for_parent->myNxy = 0; + data_for_parent->myNyx = 0; + data_for_parent->myNyz = 0; + data_for_parent->myNzy = 0; + data_for_parent->myNzx = 0; + data_for_parent->myNxz = 0; +#if TAYLOR_SERIES_ORDER >= 2 + data_for_parent->myNijkDiag = child_data_array[0].myNijkDiag; + data_for_parent->mySumPermuteNxyz = child_data_array[0].mySumPermuteNxyz; + data_for_parent->my2Nxxy_Nyxx = child_data_array[0].my2Nxxy_Nyxx; + data_for_parent->my2Nxxz_Nzxx = child_data_array[0].my2Nxxz_Nzxx; + data_for_parent->my2Nyyz_Nzyy = child_data_array[0].my2Nyyz_Nzyy; + data_for_parent->my2Nyyx_Nxyy = child_data_array[0].my2Nyyx_Nxyy; + data_for_parent->my2Nzzx_Nxzz = child_data_array[0].my2Nzzx_Nxzz; + data_for_parent->my2Nzzy_Nyzz = child_data_array[0].my2Nzzy_Nyzz; +#endif + + for (int i = 1; i < nchildren; ++i) + { + data_for_parent->myNijDiag += child_data_array[i].myNijDiag; +#if TAYLOR_SERIES_ORDER >= 2 + data_for_parent->myNijkDiag += child_data_array[i].myNijkDiag; + data_for_parent->mySumPermuteNxyz += child_data_array[i].mySumPermuteNxyz; + data_for_parent->my2Nxxy_Nyxx += child_data_array[i].my2Nxxy_Nyxx; + data_for_parent->my2Nxxz_Nzxx += child_data_array[i].my2Nxxz_Nzxx; + data_for_parent->my2Nyyz_Nzyy += child_data_array[i].my2Nyyz_Nzyy; + data_for_parent->my2Nyyx_Nxyy += child_data_array[i].my2Nyyx_Nxyy; + data_for_parent->my2Nzzx_Nxzz += child_data_array[i].my2Nzzx_Nxzz; + data_for_parent->my2Nzzy_Nyzz += child_data_array[i].my2Nzzy_Nyzz; +#endif + } + for (int j = 0; j < 3; ++j) + ((T*)¤t_box_data.myNijDiag[j])[0] = child_data_array[0].myNijDiag[j]; + ((T*)¤t_box_data.myNxy_Nyx)[0] = child_data_array[0].myNxy + child_data_array[0].myNyx; + ((T*)¤t_box_data.myNyz_Nzy)[0] = child_data_array[0].myNyz + child_data_array[0].myNzy; + ((T*)¤t_box_data.myNzx_Nxz)[0] = child_data_array[0].myNzx + child_data_array[0].myNxz; + for (int j = 0; j < 3; ++j) + ((T*)¤t_box_data.myNijkDiag[j])[0] = child_data_array[0].myNijkDiag[j]; + ((T*)¤t_box_data.mySumPermuteNxyz)[0] = child_data_array[0].mySumPermuteNxyz; + ((T*)¤t_box_data.my2Nxxy_Nyxx)[0] = child_data_array[0].my2Nxxy_Nyxx; + ((T*)¤t_box_data.my2Nxxz_Nzxx)[0] = child_data_array[0].my2Nxxz_Nzxx; + ((T*)¤t_box_data.my2Nyyz_Nzyy)[0] = child_data_array[0].my2Nyyz_Nzyy; + ((T*)¤t_box_data.my2Nyyx_Nxyy)[0] = child_data_array[0].my2Nyyx_Nxyy; + ((T*)¤t_box_data.my2Nzzx_Nxzz)[0] = child_data_array[0].my2Nzzx_Nxzz; + ((T*)¤t_box_data.my2Nzzy_Nyzz)[0] = child_data_array[0].my2Nzzy_Nyzz; + for (int i = 1; i < nchildren; ++i) + { + for (int j = 0; j < 3; ++j) + ((T*)¤t_box_data.myNijDiag[j])[i] = child_data_array[i].myNijDiag[j]; + ((T*)¤t_box_data.myNxy_Nyx)[i] = child_data_array[i].myNxy + child_data_array[i].myNyx; + ((T*)¤t_box_data.myNyz_Nzy)[i] = child_data_array[i].myNyz + child_data_array[i].myNzy; + ((T*)¤t_box_data.myNzx_Nxz)[i] = child_data_array[i].myNzx + child_data_array[i].myNxz; + for (int j = 0; j < 3; ++j) + ((T*)¤t_box_data.myNijkDiag[j])[i] = child_data_array[i].myNijkDiag[j]; + ((T*)¤t_box_data.mySumPermuteNxyz)[i] = child_data_array[i].mySumPermuteNxyz; + ((T*)¤t_box_data.my2Nxxy_Nyxx)[i] = child_data_array[i].my2Nxxy_Nyxx; + ((T*)¤t_box_data.my2Nxxz_Nzxx)[i] = child_data_array[i].my2Nxxz_Nzxx; + ((T*)¤t_box_data.my2Nyyz_Nzyy)[i] = child_data_array[i].my2Nyyz_Nzyy; + ((T*)¤t_box_data.my2Nyyx_Nxyy)[i] = child_data_array[i].my2Nyyx_Nxyy; + ((T*)¤t_box_data.my2Nzzx_Nxzz)[i] = child_data_array[i].my2Nzzx_Nxzz; + ((T*)¤t_box_data.my2Nzzy_Nyzz)[i] = child_data_array[i].my2Nzzy_Nyzz; + } + for (int i = nchildren; i < BVH_N; ++i) + { + // Set to zero, just to avoid false positives for uses of uninitialized memory. + for (int j = 0; j < 3; ++j) + ((T*)¤t_box_data.myNijDiag[j])[i] = 0; + ((T*)¤t_box_data.myNxy_Nyx)[i] = 0; + ((T*)¤t_box_data.myNyz_Nzy)[i] = 0; + ((T*)¤t_box_data.myNzx_Nxz)[i] = 0; + for (int j = 0; j < 3; ++j) + ((T*)¤t_box_data.myNijkDiag[j])[i] = 0; + ((T*)¤t_box_data.mySumPermuteNxyz)[i] = 0; + ((T*)¤t_box_data.my2Nxxy_Nyxx)[i] = 0; + ((T*)¤t_box_data.my2Nxxz_Nzxx)[i] = 0; + ((T*)¤t_box_data.my2Nyyz_Nzyy)[i] = 0; + ((T*)¤t_box_data.my2Nyyx_Nxyy)[i] = 0; + ((T*)¤t_box_data.my2Nzzx_Nxzz)[i] = 0; + ((T*)¤t_box_data.my2Nzzy_Nyzz)[i] = 0; + } + + for (int i = 0; i < nchildren; ++i) + { + const LocalData &child_data = child_data_array[i]; + UT_Vector3T displacement = child_data.myAverageP - UT_Vector3T(data_for_parent->myAverageP); + UT_Vector3T N = child_data.myN; + + // Adjust Nij for the change in centre P + data_for_parent->myNijDiag += N*displacement; + T Nxy = child_data.myNxy + N[0]*displacement[1]; + T Nyx = child_data.myNyx + N[1]*displacement[0]; + T Nyz = child_data.myNyz + N[1]*displacement[2]; + T Nzy = child_data.myNzy + N[2]*displacement[1]; + T Nzx = child_data.myNzx + N[2]*displacement[0]; + T Nxz = child_data.myNxz + N[0]*displacement[2]; + + data_for_parent->myNxy += Nxy; + data_for_parent->myNyx += Nyx; + data_for_parent->myNyz += Nyz; + data_for_parent->myNzy += Nzy; + data_for_parent->myNzx += Nzx; + data_for_parent->myNxz += Nxz; + +#if TAYLOR_SERIES_ORDER >= 2 + if (order >= 2) + { + // Adjust Nijk for the change in centre P + data_for_parent->myNijkDiag += T(2)*displacement*child_data.myNijDiag + displacement*displacement*child_data.myN; + data_for_parent->mySumPermuteNxyz += (displacement[0]*(Nyz+Nzy) + displacement[1]*(Nzx+Nxz) + displacement[2]*(Nxy+Nyx)); + data_for_parent->my2Nxxy_Nyxx += + 2*(displacement[1]*child_data.myNijDiag[0] + displacement[0]*child_data.myNxy + N[0]*displacement[0]*displacement[1]) + + 2*child_data.myNyx*displacement[0] + N[1]*displacement[0]*displacement[0]; + data_for_parent->my2Nxxz_Nzxx += + 2*(displacement[2]*child_data.myNijDiag[0] + displacement[0]*child_data.myNxz + N[0]*displacement[0]*displacement[2]) + + 2*child_data.myNzx*displacement[0] + N[2]*displacement[0]*displacement[0]; + data_for_parent->my2Nyyz_Nzyy += + 2*(displacement[2]*child_data.myNijDiag[1] + displacement[1]*child_data.myNyz + N[1]*displacement[1]*displacement[2]) + + 2*child_data.myNzy*displacement[1] + N[2]*displacement[1]*displacement[1]; + data_for_parent->my2Nyyx_Nxyy += + 2*(displacement[0]*child_data.myNijDiag[1] + displacement[1]*child_data.myNyx + N[1]*displacement[1]*displacement[0]) + + 2*child_data.myNxy*displacement[1] + N[0]*displacement[1]*displacement[1]; + data_for_parent->my2Nzzx_Nxzz += + 2*(displacement[0]*child_data.myNijDiag[2] + displacement[2]*child_data.myNzx + N[2]*displacement[2]*displacement[0]) + + 2*child_data.myNxz*displacement[2] + N[0]*displacement[2]*displacement[2]; + data_for_parent->my2Nzzy_Nyzz += + 2*(displacement[1]*child_data.myNijDiag[2] + displacement[2]*child_data.myNzy + N[2]*displacement[2]*displacement[1]) + + 2*child_data.myNyz*displacement[2] + N[1]*displacement[2]*displacement[2]; + } +#endif + } + } +#endif +#if SOLID_ANGLE_DEBUG + UTdebugFormat(""); + UTdebugFormat("Node {}: nchildren = {}; maxP = {}", nodei, nchildren, SYSsqrt(current_box_data.myMaxPDist2)); + UTdebugFormat(" P = {}; N = {}", current_box_data.myAverageP, current_box_data.myN); +#if TAYLOR_SERIES_ORDER >= 1 + UTdebugFormat(" Nii = {}", current_box_data.myNijDiag); + UTdebugFormat(" Nxy+Nyx = {}; Nyz+Nzy = {}; Nyz+Nzy = {}", current_box_data.myNxy_Nyx, current_box_data.myNyz_Nzy, current_box_data.myNzx_Nxz); +#if TAYLOR_SERIES_ORDER >= 2 + UTdebugFormat(" Niii = {}; 2(Nxyz+Nyzx+Nzxy) = {}", current_box_data.myNijkDiag, current_box_data.mySumPermuteNxyz); + UTdebugFormat(" 2Nxxy+Nyxx = {}; 2Nxxz+Nzxx = {}", current_box_data.my2Nxxy_Nyxx, current_box_data.my2Nxxz_Nzxx); + UTdebugFormat(" 2Nyyz+Nzyy = {}; 2Nyyx+Nxyy = {}", current_box_data.my2Nyyz_Nzyy, current_box_data.my2Nyyx_Nxyy); + UTdebugFormat(" 2Nzzx+Nxzz = {}; 2Nzzy+Nyzz = {}", current_box_data.my2Nzzx_Nxzz, current_box_data.my2Nzzy_Nyzz); +#endif +#endif +#endif + } + }; + +#if SOLID_ANGLE_TIME_PRECOMPUTE + timer.start(); +#endif + const PrecomputeFunctors functors(box_data, triangle_boxes.array(), triangle_points, positions, order); + // NOTE: post-functor relies on non-null data_for_parent, so we have to pass one. + LocalData local_data; + myTree.template traverseParallel(4096, functors, &local_data); + //myTree.template traverse(functors); +#if SOLID_ANGLE_TIME_PRECOMPUTE + time = timer.stop(); + UTdebugFormat("{} s to precompute coefficients.", time); +#endif +} + +template +inline void UT_SolidAngle::clear() +{ + myTree.clear(); + myNBoxes = 0; + myOrder = 2; + myData.reset(); + myNTriangles = 0; + myTrianglePoints = nullptr; + myNPoints = 0; + myPositions = nullptr; +} + +template +inline T UT_SolidAngle::computeSolidAngle(const UT_Vector3T &query_point, const T accuracy_scale) const +{ + const T accuracy_scale2 = accuracy_scale*accuracy_scale; + + struct SolidAngleFunctors + { + const BoxData *const myBoxData; + const UT_Vector3T myQueryPoint; + const T myAccuracyScale2; + const UT_Vector3T *const myPositions; + const int *const myTrianglePoints; + const int myOrder; + + SolidAngleFunctors( + const BoxData *const box_data, + const UT_Vector3T &query_point, + const T accuracy_scale2, + const int order, + const UT_Vector3T *const positions, + const int *const triangle_points) + : myBoxData(box_data) + , myQueryPoint(query_point) + , myAccuracyScale2(accuracy_scale2) + , myPositions(positions) + , myTrianglePoints(triangle_points) + , myOrder(order) + {} + uint pre(const int nodei, T *data_for_parent) const + { + const BoxData &data = myBoxData[nodei]; + const typename BoxData::Type maxP2 = data.myMaxPDist2; + UT_FixedVector q; + q[0] = typename BoxData::Type(myQueryPoint[0]); + q[1] = typename BoxData::Type(myQueryPoint[1]); + q[2] = typename BoxData::Type(myQueryPoint[2]); + q -= data.myAverageP; + const typename BoxData::Type qlength2 = q[0]*q[0] + q[1]*q[1] + q[2]*q[2]; + + // If the query point is within a factor of accuracy_scale of the box radius, + // it's assumed to be not a good enough approximation, so it needs to descend. + // TODO: Is there a way to estimate the error? + static_assert((std::is_same::value), "FIXME: Implement support for other tuple types!"); + v4uu descend_mask = (qlength2 <= maxP2*myAccuracyScale2); + uint descend_bitmask = _mm_movemask_ps(V4SF(descend_mask.vector)); + constexpr uint allchildbits = ((uint(1)<= 1 + const int order = myOrder; + if (order >= 1) + { + const UT_FixedVector q2 = q*q; + const typename BoxData::Type qlength_m3 = qlength_m2*qlength_m1; + const typename BoxData::Type Omega_1 = + qlength_m3*(data.myNijDiag[0] + data.myNijDiag[1] + data.myNijDiag[2] + -typename BoxData::Type(3.0)*(dot(q2,data.myNijDiag) + + q[0]*q[1]*data.myNxy_Nyx + + q[0]*q[2]*data.myNzx_Nxz + + q[1]*q[2]*data.myNyz_Nzy)); + Omega_approx += Omega_1; +#if TAYLOR_SERIES_ORDER >= 2 + if (order >= 2) + { + const UT_FixedVector q3 = q2*q; + const typename BoxData::Type qlength_m4 = qlength_m2*qlength_m2; + typename BoxData::Type temp0[3] = { + data.my2Nyyx_Nxyy+data.my2Nzzx_Nxzz, + data.my2Nzzy_Nyzz+data.my2Nxxy_Nyxx, + data.my2Nxxz_Nzxx+data.my2Nyyz_Nzyy + }; + typename BoxData::Type temp1[3] = { + q[1]*data.my2Nxxy_Nyxx + q[2]*data.my2Nxxz_Nzxx, + q[2]*data.my2Nyyz_Nzyy + q[0]*data.my2Nyyx_Nxyy, + q[0]*data.my2Nzzx_Nxzz + q[1]*data.my2Nzzy_Nyzz + }; + const typename BoxData::Type Omega_2 = + qlength_m4*(typename BoxData::Type(1.5)*dot(q, typename BoxData::Type(3)*data.myNijkDiag + UT_FixedVector(temp0)) + -typename BoxData::Type(7.5)*(dot(q3,data.myNijkDiag) + q[0]*q[1]*q[2]*data.mySumPermuteNxyz + dot(q2, UT_FixedVector(temp1)))); + Omega_approx += Omega_2; + } +#endif + } +#endif + + // If q is so small that we got NaNs and we just have a + // small bounding box, it needs to descend. + const v4uu mask = Omega_approx.isFinite() & ~descend_mask; + Omega_approx = Omega_approx & mask; + descend_bitmask = (~_mm_movemask_ps(V4SF(mask.vector))) & allchildbits; + + T sum = Omega_approx[0]; + for (int i = 1; i < BVH_N; ++i) + sum += Omega_approx[i]; + *data_for_parent = sum; + + return descend_bitmask; + } + void item(const int itemi, const int /*parent_nodei*/, T &data_for_parent) const + { + const UT_Vector3T *const positions = myPositions; + const int *const cur_triangle_points = myTrianglePoints + 3*itemi; + const UT_Vector3T a = positions[cur_triangle_points[0]]; + const UT_Vector3T b = positions[cur_triangle_points[1]]; + const UT_Vector3T c = positions[cur_triangle_points[2]]; + + data_for_parent = UTsignedSolidAngleTri(a, b, c, myQueryPoint); + } + SYS_FORCE_INLINE void post(const int /*nodei*/, const int /*parent_nodei*/, T *data_for_parent, const int nchildren, const T *child_data_array, const uint descend_bits) const + { + T sum = (descend_bits&1) ? child_data_array[0] : 0; + for (int i = 1; i < nchildren; ++i) + sum += ((descend_bits>>i)&1) ? child_data_array[i] : 0; + + *data_for_parent += sum; + } + }; + const SolidAngleFunctors functors(myData.get(), query_point, accuracy_scale2, myOrder, myPositions, myTrianglePoints); + + T sum; + myTree.traverseVector(functors, &sum); + return sum; +} + +template +struct UT_SubtendedAngle::BoxData +{ + void clear() + { + // Set everything to zero + memset(this,0,sizeof(*this)); + } + + using Type = typename std::conditional::value, v4uf, UT_FixedVector>::type; + using SType = typename std::conditional::value, v4uf, UT_FixedVector>::type; + + /// An upper bound on the squared distance from myAverageP to the farthest point in the box. + SType myMaxPDist2; + + /// Centre of mass of the mesh surface in this box + UT_FixedVector myAverageP; + + /// Unnormalized, area-weighted normal of the mesh in this box + UT_FixedVector myN; + + /// Values for Omega_1 + /// @{ + UT_FixedVector myNijDiag; // Nxx, Nyy + Type myNxy_Nyx; // Nxy+Nyx + /// @} + + /// Values for Omega_2 + /// @{ + UT_FixedVector myNijkDiag; // Nxxx, Nyyy + Type my2Nxxy_Nyxx; // Nxxy+Nxyx+Nyxx = 2Nxxy+Nyxx + Type my2Nyyx_Nxyy; // Nyyx+Nyxy+Nxyy = 2Nyyx+Nxyy + /// @} +}; + +template +inline UT_SubtendedAngle::UT_SubtendedAngle() + : myTree() + , myNBoxes(0) + , myOrder(2) + , myData(nullptr) + , myNSegments(0) + , mySegmentPoints(nullptr) + , myNPoints(0) + , myPositions(nullptr) +{} + +template +inline UT_SubtendedAngle::~UT_SubtendedAngle() +{ + // Default destruction works, but this needs to be outlined + // to avoid having to include UT_BVHImpl.h in the header, + // (for the UT_UniquePtr destructor.) +} + +template +inline void UT_SubtendedAngle::init( + const int nsegments, + const int *const segment_points, + const int npoints, + const UT_Vector2T *const positions, + const int order) +{ +#if SOLID_ANGLE_DEBUG + UTdebugFormat(""); + UTdebugFormat(""); + UTdebugFormat("Building BVH for {} segments on {} points:", nsegments, npoints); +#endif + myOrder = order; + myNSegments = nsegments; + mySegmentPoints = segment_points; + myNPoints = npoints; + myPositions = positions; + +#if SOLID_ANGLE_TIME_PRECOMPUTE + UT_StopWatch timer; + timer.start(); +#endif + UT_SmallArray> segment_boxes; + segment_boxes.setSizeNoInit(nsegments); + if (nsegments < 16*1024) + { + const int *cur_segment_points = segment_points; + for (int i = 0; i < nsegments; ++i, cur_segment_points += 2) + { + UT::Box &box = segment_boxes[i]; + box.initBounds(positions[cur_segment_points[0]]); + box.enlargeBounds(positions[cur_segment_points[1]]); + } + } + else + { + igl::parallel_for(nsegments, + [segment_points,&segment_boxes,positions](int i) + { + const int *cur_segment_points = segment_points + i*2; + UT::Box &box = segment_boxes[i]; + box.initBounds(positions[cur_segment_points[0]]); + box.enlargeBounds(positions[cur_segment_points[1]]); + }); + } +#if SOLID_ANGLE_TIME_PRECOMPUTE + double time = timer.stop(); + UTdebugFormat("{} s to create bounding boxes.", time); + timer.start(); +#endif + myTree.template init(segment_boxes.array(), nsegments); +#if SOLID_ANGLE_TIME_PRECOMPUTE + time = timer.stop(); + UTdebugFormat("{} s to initialize UT_BVH structure. {} nodes", time, myTree.getNumNodes()); +#endif + + //myTree.debugDump(); + + const int nnodes = myTree.getNumNodes(); + + myNBoxes = nnodes; + BoxData *box_data = new BoxData[nnodes]; + myData.reset(box_data); + + // Some data are only needed during initialization. + struct LocalData + { + // Bounding box + UT::Box myBox; + + // P and N are needed from each child for computing Nij. + UT_Vector2T myAverageP; + UT_Vector2T myLengthP; + UT_Vector2T myN; + + // Unsigned length is needed for computing the average position. + T myLength; + + // These are needed for computing Nijk. + UT_Vector2T myNijDiag; + T myNxy; T myNyx; + + UT_Vector2T myNijkDiag; // Nxxx, Nyyy + T my2Nxxy_Nyxx; // Nxxy+Nxyx+Nyxx = 2Nxxy+Nyxx + T my2Nyyx_Nxyy; // Nyyx+Nyxy+Nxyy = 2Nyyx+Nxyy + }; + + struct PrecomputeFunctors + { + BoxData *const myBoxData; + const UT::Box *const mySegmentBoxes; + const int *const mySegmentPoints; + const UT_Vector2T *const myPositions; + const int myOrder; + + PrecomputeFunctors( + BoxData *box_data, + const UT::Box *segment_boxes, + const int *segment_points, + const UT_Vector2T *positions, + const int order) + : myBoxData(box_data) + , mySegmentBoxes(segment_boxes) + , mySegmentPoints(segment_points) + , myPositions(positions) + , myOrder(order) + {} + constexpr SYS_FORCE_INLINE bool pre(const int /*nodei*/, LocalData * /*data_for_parent*/) const + { + return true; + } + void item(const int itemi, const int /*parent_nodei*/, LocalData &data_for_parent) const + { + const UT_Vector2T *const positions = myPositions; + const int *const cur_segment_points = mySegmentPoints + 2*itemi; + const UT_Vector2T a = positions[cur_segment_points[0]]; + const UT_Vector2T b = positions[cur_segment_points[1]]; + const UT_Vector2T ab = b-a; + + const UT::Box &segment_box = mySegmentBoxes[itemi]; + data_for_parent.myBox = segment_box; + + // Length-weighted normal (unnormalized) + UT_Vector2T N; + N[0] = ab[1]; + N[1] = -ab[0]; + const T length2 = ab.length2(); + const T length = SYSsqrt(length2); + const UT_Vector2T P = T(0.5)*(a+b); + data_for_parent.myAverageP = P; + data_for_parent.myLengthP = P*length; + data_for_parent.myN = N; +#if SOLID_ANGLE_DEBUG + UTdebugFormat(""); + UTdebugFormat("Triangle {}: P = {}; N = {}; length = {}", itemi, P, N, length); + UTdebugFormat(" box = {}", data_for_parent.myBox); +#endif + + data_for_parent.myLength = length; + const int order = myOrder; + if (order < 1) + return; + + // NOTE: Due to P being at the centroid, segments have Nij = 0 + // contributions to Nij. + data_for_parent.myNijDiag = T(0); + data_for_parent.myNxy = 0; data_for_parent.myNyx = 0; + + if (order < 2) + return; + + // If it's zero-length, the results are zero, so we can skip. + if (length == 0) + { + data_for_parent.myNijkDiag = T(0); + data_for_parent.my2Nxxy_Nyxx = 0; + data_for_parent.my2Nyyx_Nxyy = 0; + return; + } + + T integral_xx = ab[0]*ab[0]/T(12); + T integral_xy = ab[0]*ab[1]/T(12); + T integral_yy = ab[1]*ab[1]/T(12); + data_for_parent.myNijkDiag[0] = integral_xx*N[0]; + data_for_parent.myNijkDiag[1] = integral_yy*N[1]; + T Nxxy = N[0]*integral_xy; + T Nyxx = N[1]*integral_xx; + T Nyyx = N[1]*integral_xy; + T Nxyy = N[0]*integral_yy; + data_for_parent.my2Nxxy_Nyxx = 2*Nxxy + Nyxx; + data_for_parent.my2Nyyx_Nxyy = 2*Nyyx + Nxyy; +#if SOLID_ANGLE_DEBUG + UTdebugFormat(" integral_xx = {}; yy = {}", integral_xx, integral_yy); + UTdebugFormat(" integral_xy = {}", integral_xy); +#endif + } + + void post(const int nodei, const int /*parent_nodei*/, LocalData *data_for_parent, const int nchildren, const LocalData *child_data_array) const + { + // NOTE: Although in the general case, data_for_parent may be null for the root call, + // this functor assumes that it's non-null, so the call below must pass a non-null pointer. + + BoxData ¤t_box_data = myBoxData[nodei]; + + UT_Vector2T N = child_data_array[0].myN; + ((T*)¤t_box_data.myN[0])[0] = N[0]; + ((T*)¤t_box_data.myN[1])[0] = N[1]; + UT_Vector2T lengthP = child_data_array[0].myLengthP; + T length = child_data_array[0].myLength; + const UT_Vector2T local_P = child_data_array[0].myAverageP; + ((T*)¤t_box_data.myAverageP[0])[0] = local_P[0]; + ((T*)¤t_box_data.myAverageP[1])[0] = local_P[1]; + for (int i = 1; i < nchildren; ++i) + { + const UT_Vector2T local_N = child_data_array[i].myN; + N += local_N; + ((T*)¤t_box_data.myN[0])[i] = local_N[0]; + ((T*)¤t_box_data.myN[1])[i] = local_N[1]; + lengthP += child_data_array[i].myLengthP; + length += child_data_array[i].myLength; + const UT_Vector2T local_P = child_data_array[i].myAverageP; + ((T*)¤t_box_data.myAverageP[0])[i] = local_P[0]; + ((T*)¤t_box_data.myAverageP[1])[i] = local_P[1]; + } + for (int i = nchildren; i < BVH_N; ++i) + { + // Set to zero, just to avoid false positives for uses of uninitialized memory. + ((T*)¤t_box_data.myN[0])[i] = 0; + ((T*)¤t_box_data.myN[1])[i] = 0; + ((T*)¤t_box_data.myAverageP[0])[i] = 0; + ((T*)¤t_box_data.myAverageP[1])[i] = 0; + } + data_for_parent->myN = N; + data_for_parent->myLengthP = lengthP; + data_for_parent->myLength = length; + + UT::Box box(child_data_array[0].myBox); + for (int i = 1; i < nchildren; ++i) + box.combine(child_data_array[i].myBox); + + // Normalize P + UT_Vector2T averageP; + if (length > 0) + averageP = lengthP/length; + else + averageP = T(0.5)*(box.getMin() + box.getMax()); + data_for_parent->myAverageP = averageP; + + data_for_parent->myBox = box; + + for (int i = 0; i < nchildren; ++i) + { + const UT::Box &local_box(child_data_array[i].myBox); + const UT_Vector2T &local_P = child_data_array[i].myAverageP; + const UT_Vector2T maxPDiff = SYSmax(local_P-UT_Vector2T(local_box.getMin()), UT_Vector2T(local_box.getMax())-local_P); + ((T*)¤t_box_data.myMaxPDist2)[i] = maxPDiff.length2(); + } + for (int i = nchildren; i < BVH_N; ++i) + { + // This child is non-existent. If we set myMaxPDist2 to infinity, it will never + // use the approximation, and the traverseVector function can check for EMPTY. + ((T*)¤t_box_data.myMaxPDist2)[i] = std::numeric_limits::infinity(); + } + + const int order = myOrder; + if (order >= 1) + { + // We now have the current box's P, so we can adjust Nij and Nijk + data_for_parent->myNijDiag = child_data_array[0].myNijDiag; + data_for_parent->myNxy = 0; + data_for_parent->myNyx = 0; + data_for_parent->myNijkDiag = child_data_array[0].myNijkDiag; + data_for_parent->my2Nxxy_Nyxx = child_data_array[0].my2Nxxy_Nyxx; + data_for_parent->my2Nyyx_Nxyy = child_data_array[0].my2Nyyx_Nxyy; + + for (int i = 1; i < nchildren; ++i) + { + data_for_parent->myNijDiag += child_data_array[i].myNijDiag; + data_for_parent->myNijkDiag += child_data_array[i].myNijkDiag; + data_for_parent->my2Nxxy_Nyxx += child_data_array[i].my2Nxxy_Nyxx; + data_for_parent->my2Nyyx_Nxyy += child_data_array[i].my2Nyyx_Nxyy; + } + for (int j = 0; j < 2; ++j) + ((T*)¤t_box_data.myNijDiag[j])[0] = child_data_array[0].myNijDiag[j]; + ((T*)¤t_box_data.myNxy_Nyx)[0] = child_data_array[0].myNxy + child_data_array[0].myNyx; + for (int j = 0; j < 2; ++j) + ((T*)¤t_box_data.myNijkDiag[j])[0] = child_data_array[0].myNijkDiag[j]; + ((T*)¤t_box_data.my2Nxxy_Nyxx)[0] = child_data_array[0].my2Nxxy_Nyxx; + ((T*)¤t_box_data.my2Nyyx_Nxyy)[0] = child_data_array[0].my2Nyyx_Nxyy; + for (int i = 1; i < nchildren; ++i) + { + for (int j = 0; j < 2; ++j) + ((T*)¤t_box_data.myNijDiag[j])[i] = child_data_array[i].myNijDiag[j]; + ((T*)¤t_box_data.myNxy_Nyx)[i] = child_data_array[i].myNxy + child_data_array[i].myNyx; + for (int j = 0; j < 2; ++j) + ((T*)¤t_box_data.myNijkDiag[j])[i] = child_data_array[i].myNijkDiag[j]; + ((T*)¤t_box_data.my2Nxxy_Nyxx)[i] = child_data_array[i].my2Nxxy_Nyxx; + ((T*)¤t_box_data.my2Nyyx_Nxyy)[i] = child_data_array[i].my2Nyyx_Nxyy; + } + for (int i = nchildren; i < BVH_N; ++i) + { + // Set to zero, just to avoid false positives for uses of uninitialized memory. + for (int j = 0; j < 2; ++j) + ((T*)¤t_box_data.myNijDiag[j])[i] = 0; + ((T*)¤t_box_data.myNxy_Nyx)[i] = 0; + for (int j = 0; j < 2; ++j) + ((T*)¤t_box_data.myNijkDiag[j])[i] = 0; + ((T*)¤t_box_data.my2Nxxy_Nyxx)[i] = 0; + ((T*)¤t_box_data.my2Nyyx_Nxyy)[i] = 0; + } + + for (int i = 0; i < nchildren; ++i) + { + const LocalData &child_data = child_data_array[i]; + UT_Vector2T displacement = child_data.myAverageP - UT_Vector2T(data_for_parent->myAverageP); + UT_Vector2T N = child_data.myN; + + // Adjust Nij for the change in centre P + data_for_parent->myNijDiag += N*displacement; + T Nxy = child_data.myNxy + N[0]*displacement[1]; + T Nyx = child_data.myNyx + N[1]*displacement[0]; + + data_for_parent->myNxy += Nxy; + data_for_parent->myNyx += Nyx; + + if (order >= 2) + { + // Adjust Nijk for the change in centre P + data_for_parent->myNijkDiag += T(2)*displacement*child_data.myNijDiag + displacement*displacement*child_data.myN; + data_for_parent->my2Nxxy_Nyxx += + 2*(displacement[1]*child_data.myNijDiag[0] + displacement[0]*child_data.myNxy + N[0]*displacement[0]*displacement[1]) + + 2*child_data.myNyx*displacement[0] + N[1]*displacement[0]*displacement[0]; + data_for_parent->my2Nyyx_Nxyy += + 2*(displacement[0]*child_data.myNijDiag[1] + displacement[1]*child_data.myNyx + N[1]*displacement[1]*displacement[0]) + + 2*child_data.myNxy*displacement[1] + N[0]*displacement[1]*displacement[1]; + } + } + } +#if SOLID_ANGLE_DEBUG + UTdebugFormat(""); + UTdebugFormat("Node {}: nchildren = {}; maxP = {}", nodei, nchildren, SYSsqrt(current_box_data.myMaxPDist2)); + UTdebugFormat(" P = {}; N = {}", current_box_data.myAverageP, current_box_data.myN); + UTdebugFormat(" Nii = {}", current_box_data.myNijDiag); + UTdebugFormat(" Nxy+Nyx = {}", current_box_data.myNxy_Nyx); + UTdebugFormat(" Niii = {}", current_box_data.myNijkDiag); + UTdebugFormat(" 2Nxxy+Nyxx = {}; 2Nyyx+Nxyy = {}", current_box_data.my2Nxxy_Nyxx, current_box_data.my2Nyyx_Nxyy); +#endif + } + }; + +#if SOLID_ANGLE_TIME_PRECOMPUTE + timer.start(); +#endif + const PrecomputeFunctors functors(box_data, segment_boxes.array(), segment_points, positions, order); + // NOTE: post-functor relies on non-null data_for_parent, so we have to pass one. + LocalData local_data; + myTree.template traverseParallel(4096, functors, &local_data); + //myTree.template traverse(functors); +#if SOLID_ANGLE_TIME_PRECOMPUTE + time = timer.stop(); + UTdebugFormat("{} s to precompute coefficients.", time); +#endif +} + +template +inline void UT_SubtendedAngle::clear() +{ + myTree.clear(); + myNBoxes = 0; + myOrder = 2; + myData.reset(); + myNSegments = 0; + mySegmentPoints = nullptr; + myNPoints = 0; + myPositions = nullptr; +} + +template +inline T UT_SubtendedAngle::computeAngle(const UT_Vector2T &query_point, const T accuracy_scale) const +{ + const T accuracy_scale2 = accuracy_scale*accuracy_scale; + + struct AngleFunctors + { + const BoxData *const myBoxData; + const UT_Vector2T myQueryPoint; + const T myAccuracyScale2; + const UT_Vector2T *const myPositions; + const int *const mySegmentPoints; + const int myOrder; + + AngleFunctors( + const BoxData *const box_data, + const UT_Vector2T &query_point, + const T accuracy_scale2, + const int order, + const UT_Vector2T *const positions, + const int *const segment_points) + : myBoxData(box_data) + , myQueryPoint(query_point) + , myAccuracyScale2(accuracy_scale2) + , myOrder(order) + , myPositions(positions) + , mySegmentPoints(segment_points) + {} + uint pre(const int nodei, T *data_for_parent) const + { + const BoxData &data = myBoxData[nodei]; + const typename BoxData::Type maxP2 = data.myMaxPDist2; + UT_FixedVector q; + q[0] = typename BoxData::Type(myQueryPoint[0]); + q[1] = typename BoxData::Type(myQueryPoint[1]); + q -= data.myAverageP; + const typename BoxData::Type qlength2 = q[0]*q[0] + q[1]*q[1]; + + // If the query point is within a factor of accuracy_scale of the box radius, + // it's assumed to be not a good enough approximation, so it needs to descend. + // TODO: Is there a way to estimate the error? + static_assert((std::is_same::value), "FIXME: Implement support for other tuple types!"); + v4uu descend_mask = (qlength2 <= maxP2*myAccuracyScale2); + uint descend_bitmask = _mm_movemask_ps(V4SF(descend_mask.vector)); + constexpr uint allchildbits = ((uint(1)<= 1) + { + const UT_FixedVector q2 = q*q; + const typename BoxData::Type Omega_1 = + qlength_m2*(data.myNijDiag[0] + data.myNijDiag[1] + -typename BoxData::Type(2.0)*(dot(q2,data.myNijDiag) + + q[0]*q[1]*data.myNxy_Nyx)); + Omega_approx += Omega_1; + if (order >= 2) + { + const UT_FixedVector q3 = q2*q; + const typename BoxData::Type qlength_m3 = qlength_m2*qlength_m1; + typename BoxData::Type temp0[2] = { + data.my2Nyyx_Nxyy, + data.my2Nxxy_Nyxx + }; + typename BoxData::Type temp1[2] = { + q[1]*data.my2Nxxy_Nyxx, + q[0]*data.my2Nyyx_Nxyy + }; + const typename BoxData::Type Omega_2 = + qlength_m3*(dot(q, typename BoxData::Type(3)*data.myNijkDiag + UT_FixedVector(temp0)) + -typename BoxData::Type(4.0)*(dot(q3,data.myNijkDiag) + dot(q2, UT_FixedVector(temp1)))); + Omega_approx += Omega_2; + } + } + + // If q is so small that we got NaNs and we just have a + // small bounding box, it needs to descend. + const v4uu mask = Omega_approx.isFinite() & ~descend_mask; + Omega_approx = Omega_approx & mask; + descend_bitmask = (~_mm_movemask_ps(V4SF(mask.vector))) & allchildbits; + + T sum = Omega_approx[0]; + for (int i = 1; i < BVH_N; ++i) + sum += Omega_approx[i]; + *data_for_parent = sum; + + return descend_bitmask; + } + void item(const int itemi, const int /*parent_nodei*/, T &data_for_parent) const + { + const UT_Vector2T *const positions = myPositions; + const int *const cur_segment_points = mySegmentPoints + 2*itemi; + const UT_Vector2T a = positions[cur_segment_points[0]]; + const UT_Vector2T b = positions[cur_segment_points[1]]; + + data_for_parent = UTsignedAngleSegment(a, b, myQueryPoint); + } + SYS_FORCE_INLINE void post(const int /*nodei*/, const int /*parent_nodei*/, T *data_for_parent, const int nchildren, const T *child_data_array, const uint descend_bits) const + { + T sum = (descend_bits&1) ? child_data_array[0] : 0; + for (int i = 1; i < nchildren; ++i) + sum += ((descend_bits>>i)&1) ? child_data_array[i] : 0; + + *data_for_parent += sum; + } + }; + const AngleFunctors functors(myData.get(), query_point, accuracy_scale2, myOrder, myPositions, mySegmentPoints); + + T sum; + myTree.traverseVector(functors, &sum); + return sum; +} + +// Instantiate our templates. +//template class UT_SolidAngle; +// FIXME: The SIMD parts will need to be handled differently in order to support fpreal64. +//template class UT_SolidAngle; +//template class UT_SolidAngle; +//template class UT_SubtendedAngle; +//template class UT_SubtendedAngle; +//template class UT_SubtendedAngle; + +} // End HDK_Sample namespace +}} diff --git a/src/vendor/igl/LICENSE.MPL2 b/src/vendor/igl/LICENSE.MPL2 new file mode 100644 index 0000000..14e2f77 --- /dev/null +++ b/src/vendor/igl/LICENSE.MPL2 @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/src/vendor/igl/PI.h b/src/vendor/igl/PI.h new file mode 100644 index 0000000..8379e37 --- /dev/null +++ b/src/vendor/igl/PI.h @@ -0,0 +1,21 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PI_H +#define IGL_PI_H +namespace igl +{ + // Use standard mathematical constants' M_PI if available +#ifdef M_PI + /// π + constexpr double PI = M_PI; +#else + /// π + constexpr double PI = 3.1415926535897932384626433832795; +#endif +} +#endif diff --git a/src/vendor/igl/default_num_threads.cpp b/src/vendor/igl/default_num_threads.cpp new file mode 100644 index 0000000..21bffa9 --- /dev/null +++ b/src/vendor/igl/default_num_threads.cpp @@ -0,0 +1,66 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2021 Jérémie Dumas +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "default_num_threads.h" + +#include +#include + +IGL_INLINE unsigned int igl::default_num_threads(unsigned int user_num_threads) { + // Thread-safe initialization using Meyers' singleton + class MySingleton { + public: + static MySingleton &instance(unsigned int force_num_threads) { + static MySingleton instance(force_num_threads); + return instance; + } + + unsigned int get_num_threads() const { return m_num_threads; } + + private: + static const char* getenv_nowarning(const char* env_var) + { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + return std::getenv(env_var); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + + MySingleton(unsigned int force_num_threads) { + // User-defined default + if (force_num_threads) { + m_num_threads = force_num_threads; + return; + } + // Set from env var + if (const char *env_str = getenv_nowarning("IGL_NUM_THREADS")) { + const int env_num_thread = atoi(env_str); + if (env_num_thread > 0) { + m_num_threads = static_cast(env_num_thread); + return; + } + } + // Guess from hardware + const unsigned int hw_num_threads = std::thread::hardware_concurrency(); + if (hw_num_threads) { + m_num_threads = hw_num_threads; + return; + } + // Fallback when std::thread::hardware_concurrency doesn't work + m_num_threads = 8u; + } + + unsigned int m_num_threads = 0; + }; + + return MySingleton::instance(user_num_threads).get_num_threads(); +} diff --git a/src/vendor/igl/default_num_threads.h b/src/vendor/igl/default_num_threads.h new file mode 100644 index 0000000..e1e8d1b --- /dev/null +++ b/src/vendor/igl/default_num_threads.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2021 Jérémie Dumas +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DEFAULT_NUM_THREADS_H +#define IGL_DEFAULT_NUM_THREADS_H +#include "igl_inline.h" + +namespace igl +{ + /// + /// Returns the default number of threads used in libigl. The value returned by the first call to + /// this function is cached. The following strategy is used to determine the default number of + /// threads: + /// 1. User-provided argument force_num_threads if != 0. + /// 2. Environment variable IGL_NUM_THREADS if > 0. + /// 3. Hardware concurrency if != 0. + /// 4. A fallback value of 8 is used otherwise. + /// + /// @note It is safe to call this method from multiple threads. + /// + /// @param[in] force_num_threads User-provided default value. + /// + /// @return Default number of threads. + /// + IGL_INLINE unsigned int default_num_threads(unsigned int force_num_threads = 0); +} + +#ifndef IGL_STATIC_LIBRARY +#include "default_num_threads.cpp" +#endif + +#endif diff --git a/src/vendor/igl/fast_winding_number.cpp b/src/vendor/igl/fast_winding_number.cpp new file mode 100644 index 0000000..78fcb2b --- /dev/null +++ b/src/vendor/igl/fast_winding_number.cpp @@ -0,0 +1,476 @@ +#include "fast_winding_number.h" +#include "octree.h" +#include "parallel_for.h" +#include "PI.h" +#include +#include + +template < + typename DerivedP, + typename DerivedA, + typename DerivedN, + typename Index, + typename DerivedCH, + typename DerivedCM, + typename DerivedR, + typename DerivedEC> +IGL_INLINE void igl::fast_winding_number( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const int expansion_order, + Eigen::PlainObjectBase& CM, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& EC) +{ + typedef typename DerivedP::Scalar real_p; + typedef typename DerivedCM::Scalar real_cm; + typedef typename DerivedR::Scalar real_r; + typedef typename DerivedEC::Scalar real_ec; + + + int m = CH.size(); + int num_terms = -1; + + assert(expansion_order < 3 && expansion_order >= 0 && "m must be less than n"); + if(expansion_order == 0){ + num_terms = 3; + } else if(expansion_order ==1){ + num_terms = 3 + 9; + } else if(expansion_order == 2){ + num_terms = 3 + 9 + 27; + } + assert(num_terms > 0); + + R.resize(m); + CM.resize(m,3); + EC.resize(m,num_terms); + EC.setZero(m,num_terms); + std::function< void(const int) > helper; + helper = [&helper, + &P,&N,&A,&point_indices,&CH,&EC,&R,&CM] + (const int index)-> void + { + Eigen::Matrix masscenter; + masscenter << 0,0,0; + Eigen::Matrix zeroth_expansion; + zeroth_expansion << 0,0,0; + real_p areatotal = 0.0; + const int num_points = point_indices[index].size(); + for(int j = 0; j < num_points; j++){ + int curr_point_index = point_indices[index][j]; + + areatotal += A(curr_point_index); + masscenter += A(curr_point_index)*P.row(curr_point_index); + zeroth_expansion += A(curr_point_index)*N.row(curr_point_index); + } + // Avoid divide by zero + if(num_points > 0) + { + masscenter = masscenter/areatotal; + }else + { + masscenter.setConstant(std::numeric_limits::quiet_NaN()); + } + CM.row(index) = masscenter; + EC.block(index,0,1,3) = zeroth_expansion; + + real_r max_norm = 0; + real_r curr_norm; + + for(int i = 0; i < point_indices[index].size(); i++){ + //Get max distance from center of mass: + int curr_point_index = point_indices[index][i]; + Eigen::Matrix point = + P.row(curr_point_index)-masscenter; + curr_norm = point.norm(); + if(curr_norm > max_norm){ + max_norm = curr_norm; + } + + //Calculate higher order terms if necessary + Eigen::Matrix TempCoeffs; + if(EC.cols() >= (3+9)){ + TempCoeffs = A(curr_point_index)*point.transpose()* + N.row(curr_point_index); + EC.block(index,3,1,9) += + Eigen::Map >(TempCoeffs.data(), + TempCoeffs.size()); + } + + if(EC.cols() == (3+9+27)){ + for(int k = 0; k < 3; k++){ + TempCoeffs = 0.5 * point(k) * (A(curr_point_index)* + point.transpose()*N.row(curr_point_index)); + EC.block(index,12+9*k,1,9) += Eigen::Map< + Eigen::Matrix >(TempCoeffs.data(), + TempCoeffs.size()); + } + } + } + + R(index) = max_norm; + if(CH(index,0) != -1) + { + for(int i = 0; i < 8; i++){ + int child = CH(index,i); + helper(child); + } + } + }; + helper(0); +} + +template < + typename DerivedP, + typename DerivedA, + typename DerivedN, + typename Index, + typename DerivedCH, + typename DerivedCM, + typename DerivedR, + typename DerivedEC, + typename DerivedQ, + typename BetaType, + typename DerivedWN> +IGL_INLINE void igl::fast_winding_number( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const Eigen::MatrixBase& CM, + const Eigen::MatrixBase& R, + const Eigen::MatrixBase& EC, + const Eigen::MatrixBase& Q, + const BetaType beta, + Eigen::PlainObjectBase& WN) +{ + + typedef typename DerivedEC::Scalar real_ec; + typedef typename DerivedQ::Scalar real_q; + typedef typename DerivedWN::Scalar real_wn; + const real_wn PI_4 = 4.0*igl::PI; + + typedef Eigen::Matrix RowVec; + + auto direct_eval = [&PI_4]( + const RowVec & loc, + const Eigen::Matrix & anorm)->real_wn + { + const typename RowVec::Scalar loc_norm = loc.norm(); + if(loc_norm == 0) + { + return 0.5; + }else + { + return (loc(0)*anorm(0)+loc(1)*anorm(1)+loc(2)*anorm(2)) + /(PI_4*(loc_norm*loc_norm*loc_norm)); + } + }; + + auto expansion_eval = + [&direct_eval,&EC,&PI_4]( + const RowVec & loc, + const int & child_index)->real_wn + { + real_wn wn; + wn = direct_eval(loc,EC.row(child_index).template head<3>()); + real_wn r = loc.norm(); + real_wn PI_4_r3; + real_wn PI_4_r5; + real_wn PI_4_r7; + if(EC.row(child_index).size()>3) + { + PI_4_r3 = PI_4*r*r*r; + PI_4_r5 = PI_4_r3*r*r; + const real_ec d = 1.0/(PI_4_r3); + Eigen::Matrix SecondDerivative = + loc.transpose()*loc*(-3.0/(PI_4_r5)); + SecondDerivative(0,0) += d; + SecondDerivative(1,1) += d; + SecondDerivative(2,2) += d; + wn += + Eigen::Map >( + SecondDerivative.data(), + SecondDerivative.size()).dot( + EC.row(child_index).template segment<9>(3)); + } + if(EC.row(child_index).size()>3+9) + { + PI_4_r7 = PI_4_r5*r*r; + const Eigen::Matrix locTloc = loc.transpose()*(loc/(PI_4_r7)); + for(int i = 0; i < 3; i++) + { + Eigen::Matrix RowCol_Diagonal = + Eigen::Matrix::Zero(3,3); + for(int u = 0;u<3;u++) + { + for(int v = 0;v<3;v++) + { + if(u==v) RowCol_Diagonal(u,v) += loc(i); + if(u==i) RowCol_Diagonal(u,v) += loc(v); + if(v==i) RowCol_Diagonal(u,v) += loc(u); + } + } + Eigen::Matrix ThirdDerivative = + 15.0*loc(i)*locTloc + (-3.0/(PI_4_r5))*(RowCol_Diagonal); + + wn += Eigen::Map >( + ThirdDerivative.data(), + ThirdDerivative.size()).dot( + EC.row(child_index).template segment<9>(12 + i*9)); + } + } + return wn; + }; + + int m = Q.rows(); + WN.resize(m,1); + + std::function< real_wn(const RowVec & , const std::vector &) > helper; + helper = [&helper, + &P,&N,&A, + &point_indices,&CH, + &CM,&R,&beta, + &direct_eval,&expansion_eval] + (const RowVec & query, const std::vector & near_indices)-> real_wn + { + real_wn wn = 0; + std::vector new_near_indices; + new_near_indices.reserve(8); + for(int i = 0; i < near_indices.size(); i++) + { + int index = near_indices[i]; + //Leaf Case, Brute force + if(CH(index,0) == -1) + { + for(int j = 0; j < point_indices[index].size(); j++) + { + int curr_row = point_indices[index][j]; + wn += direct_eval(P.row(curr_row)-query, + N.row(curr_row)*A(curr_row)); + } + } + //Non-Leaf Case + else + { + for(int child = 0; child < 8; child++) + { + int child_index = CH(index,child); + if(point_indices[child_index].size() > 0) + { + const RowVec CMciq = (CM.row(child_index)-query); + if(CMciq.norm() > beta*R(child_index)) + { + if(CH(child_index,0) == -1) + { + for(int j=0;j 0) + { + wn += helper(query,new_near_indices); + } + return wn; + }; + + if(beta > 0) + { + const std::vector near_indices_start = {0}; + igl::parallel_for(m,[&](int iter){ + WN(iter) = helper(Q.row(iter).eval(),near_indices_start); + },1000); + } else + { + igl::parallel_for(m,[&](int iter){ + double wn = 0; + for(int j = 0; j +IGL_INLINE void igl::fast_winding_number( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const Eigen::MatrixBase& Q, + const int expansion_order, + const BetaType beta, + Eigen::PlainObjectBase& WN) +{ + typedef typename DerivedWN::Scalar real; + + std::vector > point_indices; + Eigen::Matrix CH; + Eigen::Matrix CN; + Eigen::Matrix W; + + octree(P,point_indices,CH,CN,W); + + Eigen::Matrix EC; + Eigen::Matrix CM; + Eigen::Matrix R; + + fast_winding_number(P,N,A,point_indices,CH,expansion_order,CM,R,EC); + fast_winding_number(P,N,A,point_indices,CH,CM,R,EC,Q,beta,WN); +} + +template < + typename DerivedP, + typename DerivedA, + typename DerivedN, + typename DerivedQ, + typename DerivedWN> +IGL_INLINE void igl::fast_winding_number( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const Eigen::MatrixBase& Q, + Eigen::PlainObjectBase& WN) +{ + fast_winding_number(P,N,A,Q,2,2.0,WN); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedQ, + typename DerivedW> +IGL_INLINE void igl::fast_winding_number( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & Q, + Eigen::PlainObjectBase & W) +{ + igl::FastWindingNumberBVH fwn_bvh; + int order = 2; + igl::fast_winding_number(V,F,order,fwn_bvh); + float accuracy_scale = 2; + igl::fast_winding_number(fwn_bvh,accuracy_scale,Q,W); +} + +template < + typename DerivedV, + typename DerivedF> +IGL_INLINE void igl::fast_winding_number( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const int order, + FastWindingNumberBVH & fwn_bvh) +{ + assert(V.cols() == 3 && "V should be 3D"); + assert(F.cols() == 3 && "F should contain triangles"); + // Extra copies. Usuually this won't be the bottleneck. + fwn_bvh.U.resize(V.rows()); + for(int i = 0;i +IGL_INLINE void igl::fast_winding_number( + const FastWindingNumberBVH & fwn_bvh, + const float accuracy_scale, + const Eigen::MatrixBase & Q, + Eigen::PlainObjectBase & W) +{ + assert(Q.cols() == 3 && "Q should be 3D"); + W.resize(Q.rows(),1); + igl::parallel_for(Q.rows(),[&](int p) + { + FastWindingNumber::HDK_Sample::UT_Vector3TQp; + Qp[0] = Q(p,0); + Qp[1] = Q(p,1); + Qp[2] = Q(p,2); + W(p) = fwn_bvh.ut_solid_angle.computeSolidAngle(Qp,accuracy_scale) / (4.0*igl::PI); + },1000); +} + +template +IGL_INLINE typename Derivedp::Scalar igl::fast_winding_number( + const FastWindingNumberBVH & fwn_bvh, + const float accuracy_scale, + const Eigen::MatrixBase & p) +{ + assert(p.cols() == 3 && "p should be 3D"); + + FastWindingNumber::HDK_Sample::UT_Vector3TQp; + Qp[0] = p(0,0); + Qp[1] = p(0,1); + Qp[2] = p(0,2); + + typename Derivedp::Scalar w = fwn_bvh.ut_solid_angle.computeSolidAngle(Qp,accuracy_scale) / (4.0*igl::PI); + + return w; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::fast_winding_number, Eigen::Matrix >(igl::FastWindingNumberBVH const&, float, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template Eigen::Matrix::Scalar igl::fast_winding_number >(igl::FastWindingNumberBVH const&, float, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template void igl::fast_winding_number, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::fast_winding_number, Eigen::Matrix, Eigen::Matrix, int, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::fast_winding_number, Eigen::Matrix, Eigen::Matrix, int, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::fast_winding_number, Eigen::Matrix >(igl::FastWindingNumberBVH const&, float, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::fast_winding_number, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, igl::FastWindingNumberBVH&); +template void igl::fast_winding_number, Eigen::Matrix >(igl::FastWindingNumberBVH const&, float, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::fast_winding_number, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, igl::FastWindingNumberBVH&); + +// tom did this manually. Unsure how to generate otherwise... sorry. +template Eigen::Matrix::Scalar igl::fast_winding_number >(igl::FastWindingNumberBVH const&, float, Eigen::MatrixBase > const&); +template void igl::fast_winding_number, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, igl::FastWindingNumberBVH&); +template void igl::fast_winding_number, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, igl::FastWindingNumberBVH&); +#endif diff --git a/src/vendor/igl/fast_winding_number.h b/src/vendor/igl/fast_winding_number.h new file mode 100644 index 0000000..7fb00d6 --- /dev/null +++ b/src/vendor/igl/fast_winding_number.h @@ -0,0 +1,213 @@ +#ifndef IGL_FAST_WINDING_NUMBER +#define IGL_FAST_WINDING_NUMBER +#include "igl_inline.h" +#include "FastWindingNumberForSoups.h" +#include +#include +namespace igl +{ + /// Generate the precomputation for the fast winding number for point data + /// [Barill et. al 2018]. + /// + /// Given a set of 3D points P, with normals N, areas A, along with octree + /// data, and an expansion order, we define a taylor series expansion at each + /// octree cell. + /// + /// The octree data is designed to come from igl::octree, and the areas (if not + /// obtained at scan time), may be calculated using + /// igl::copyleft::cgal::point_areas. + /// + /// @param[in] P #P by 3 list of point locations + /// @param[in] N #P by 3 list of point normals + /// @param[in] A #P by 1 list of point areas + /// @param[in] point_indices a vector of vectors, where the ith entry is a vector of + /// the indices into P that are the ith octree cell's points + /// @param[in] CH #OctreeCells by 8, where the ith row is the indices of + /// the ith octree cell's children + /// @param[in] expansion_order the order of the taylor expansion. We support 0,1,2. + /// @param[out] CM #OctreeCells by 3 list of each cell's center of mass + /// @param[out] R #OctreeCells by 1 list of each cell's maximum distance of any point + /// to the center of mass + /// @param[out] EC #OctreeCells by #TaylorCoefficients list of expansion coefficients. + /// (Note that #TaylorCoefficients = ∑_{i=1}^{expansion_order} 3^i) + /// + /// \see copyleft::cgal::point_areas, knn + template < + typename DerivedP, + typename DerivedA, + typename DerivedN, + typename Index, + typename DerivedCH, + typename DerivedCM, + typename DerivedR, + typename DerivedEC> + IGL_INLINE void fast_winding_number( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const int expansion_order, + Eigen::PlainObjectBase& CM, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& EC); + /// Evaluate the fast winding number for point data, having already done the + /// the precomputation + /// + /// @param[in] P #P by 3 list of point locations + /// @param[in] N #P by 3 list of point normals + /// @param[in] A #P by 1 list of point areas + /// @param[in] point_indices a vector of vectors, where the ith entry is a vector of + /// the indices into P that are the ith octree cell's points + /// @param[in] CH #OctreeCells by 8, where the ith row is the indices of + /// the ith octree cell's children + /// @param[in] CM #OctreeCells by 3 list of each cell's center of mass + /// @param[in] R #OctreeCells by 1 list of each cell's maximum distance of any point + /// to the center of mass + /// @param[in] EC #OctreeCells by #TaylorCoefficients list of expansion coefficients. + /// (Note that #TaylorCoefficients = ∑_{i=1}^{expansion_order} 3^i) + /// @param[in] Q #Q by 3 list of query points for the winding number + /// @param[in] beta This is a Barnes-Hut style accuracy term that separates near feild + /// from far field. The higher the beta, the more accurate and slower + /// the evaluation. We reccommend using a beta value of 2. Note that + /// for a beta value ≤ 0, we use the direct evaluation, rather than + /// the fast approximation + /// @param[out] WN #Q by 1 list of windinng number values at each query point + template < + typename DerivedP, + typename DerivedA, + typename DerivedN, + typename Index, + typename DerivedCH, + typename DerivedCM, + typename DerivedR, + typename DerivedEC, + typename DerivedQ, + typename BetaType, + typename DerivedWN> + IGL_INLINE void fast_winding_number( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const Eigen::MatrixBase& CM, + const Eigen::MatrixBase& R, + const Eigen::MatrixBase& EC, + const Eigen::MatrixBase& Q, + const BetaType beta, + Eigen::PlainObjectBase& WN); + /// \overload + /// + /// \brief Evaluate the fast winding number for point data without caching the + /// precomputation. + template < + typename DerivedP, + typename DerivedA, + typename DerivedN, + typename DerivedQ, + typename BetaType, + typename DerivedWN> + IGL_INLINE void fast_winding_number( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const Eigen::MatrixBase& Q, + const int expansion_order, + const BetaType beta, + Eigen::PlainObjectBase& WN); + /// \overload + template < + typename DerivedP, + typename DerivedA, + typename DerivedN, + typename DerivedQ, + typename DerivedWN> + IGL_INLINE void fast_winding_number( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const Eigen::MatrixBase& Q, + Eigen::PlainObjectBase& WN); + /// @private + namespace FastWindingNumber { + /// @private + namespace HDK_Sample{ + /// @private + template class UT_SolidAngle;} } + /// Structure for caching precomputation for fast winding number for triangle + /// soups + struct FastWindingNumberBVH { + /// @private + FastWindingNumber::HDK_Sample::UT_SolidAngle ut_solid_angle; + // Need copies of these so they stay alive between calls. + /// @private + std::vector > U; + std::vector F; + }; + /// Compute approximate winding number of a triangle soup mesh according to + /// "Fast Winding Numbers for Soups and Clouds" [Barill et al. 2018]. + /// + /// @param[in] V #V by 3 list of mesh vertex positions + /// @param[in] F #F by 3 list of triangle mesh indices into rows of V + /// @param[in] Q #Q by 3 list of query positions + /// @param[out] W #Q list of winding number values + template < + typename DerivedV, + typename DerivedF, + typename DerivedQ, + typename DerivedW> + IGL_INLINE void fast_winding_number( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & Q, + Eigen::PlainObjectBase & W); + /// Precomputation for computing approximate winding numbers of a triangle + /// soup. + /// + /// @param[in] V #V by 3 list of mesh vertex positions + /// @param[in] F #F by 3 list of triangle mesh indices into rows of V + /// @param[in] order Taylor series expansion order to use (e.g., 2) + /// @param[out] fwn_bvh Precomputed bounding volume hierarchy + /// + template < + typename DerivedV, + typename DerivedF> + IGL_INLINE void fast_winding_number( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const int order, + FastWindingNumberBVH & fwn_bvh); + /// After precomputation, compute winding number at a each of many points in a + /// list. + /// + /// @param[in] fwn_bvh Precomputed bounding volume hierarchy + /// @param[in] accuracy_scale parameter controlling accuracy (e.g., 2) + /// @param[in] Q #Q by 3 list of query positions + /// @param[out] W #Q list of winding number values + template < + typename DerivedQ, + typename DerivedW> + IGL_INLINE void fast_winding_number( + const FastWindingNumberBVH & fwn_bvh, + const float accuracy_scale, + const Eigen::MatrixBase & Q, + Eigen::PlainObjectBase & W); + /// After precomputation, compute winding number at a single point + /// + /// @param[in] fwn_bvh Precomputed bounding volume hierarchy + /// @param[in] accuracy_scale parameter controlling accuracy (e.g., 2) + /// @param[in] p single position + /// @return w winding number of this point + template + IGL_INLINE typename Derivedp::Scalar fast_winding_number( + const FastWindingNumberBVH & fwn_bvh, + const float accuracy_scale, + const Eigen::MatrixBase & p); +} +#ifndef IGL_STATIC_LIBRARY +# include "fast_winding_number.cpp" +#endif + +#endif + diff --git a/src/vendor/igl/igl_inline.h b/src/vendor/igl/igl_inline.h new file mode 100644 index 0000000..2225664 --- /dev/null +++ b/src/vendor/igl/igl_inline.h @@ -0,0 +1,20 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// This should *NOT* be contained in a IGL_*_H ifdef, since it may be defined +// differently based on when it is included +#ifdef IGL_INLINE +#undef IGL_INLINE +#endif + +#ifndef IGL_STATIC_LIBRARY +# define IGL_INLINE inline +#else +# define IGL_INLINE +#endif + +#include diff --git a/src/vendor/igl/octree.cpp b/src/vendor/igl/octree.cpp new file mode 100644 index 0000000..61ce57a --- /dev/null +++ b/src/vendor/igl/octree.cpp @@ -0,0 +1,177 @@ +#include "octree.h" +#include + +namespace igl { + template + IGL_INLINE void octree(const Eigen::MatrixBase& P, + std::vector > & point_indices, + Eigen::PlainObjectBase& CH, + Eigen::PlainObjectBase& CN, + Eigen::PlainObjectBase& W) + { + + + + const int MAX_DEPTH = 30000; + + typedef typename DerivedCH::Scalar ChildrenType; + typedef typename DerivedCN::Scalar CentersType; + typedef typename DerivedW::Scalar WidthsType; + typedef typename DerivedP::Scalar PointScalar; + typedef Eigen::Matrix Vector8i; + typedef Eigen::Matrix RowVector3PType; + typedef Eigen::Matrix RowVector3CentersType; + + std::vector, + Eigen::aligned_allocator > > children; + std::vector, + Eigen::aligned_allocator > > centers; + std::vector widths; + + auto get_octant = [](const RowVector3PType& location, + const RowVector3CentersType& center){ + // We use a binary numbering of children. Treating the parent cell's + // center as the origin, we number the octants in the following manner: + // The first bit is 1 iff the octant's x coordinate is positive + // The second bit is 1 iff the octant's y coordinate is positive + // The third bit is 1 iff the octant's z coordinate is positive + // + // For example, the octant with negative x, positive y, positive z is: + // 110 binary = 6 decimal + IndexType index = 0; + if( location(0) >= center(0)){ + index = index + 1; + } + if( location(1) >= center(1)){ + index = index + 2; + } + if( location(2) >= center(2)){ + index = index + 4; + } + return index; + }; + + + std::function< RowVector3CentersType(const RowVector3CentersType, + const CentersType, + const ChildrenType) > + translate_center = + [](const RowVector3CentersType & parent_center, + const CentersType h, + const ChildrenType child_index){ + RowVector3CentersType change_vector; + change_vector << -h,-h,-h; + + //positive x chilren are 1,3,4,7 + if(child_index % 2){ + change_vector(0) = h; + } + //positive y children are 2,3,6,7 + if(child_index == 2 || child_index == 3 || + child_index == 6 || child_index == 7){ + change_vector(1) = h; + } + //positive z children are 4,5,6,7 + if(child_index > 3){ + change_vector(2) = h; + } + RowVector3CentersType output = parent_center + change_vector; + return output; + }; + + // How many cells do we have so far? + IndexType m = 0; + + // Useful list of number 0..7 + const Vector8i zero_to_seven = (Vector8i()<<0,1,2,3,4,5,6,7).finished(); + const Vector8i neg_ones = Vector8i::Constant(-1); + + std::function< void(const ChildrenType, const int) > helper; + // VSC and clang don't agree on whether MAX_DEPTH needs to be in the capture + // list. + helper = [&helper,&translate_center,&get_octant,&m, + &zero_to_seven,&neg_ones,&P, + &point_indices,&children,¢ers,&widths,&MAX_DEPTH] + (const ChildrenType index, const int depth)-> void + { + if(point_indices.at(index).size() > 1 && depth < MAX_DEPTH){ + //give the parent access to the children + children.at(index) = zero_to_seven.array() + m; + //make the children's data in our arrays + + //Add the children to the lists, as default children + CentersType h = widths.at(index)/2; + RowVector3CentersType curr_center = centers.at(index); + + + for(ChildrenType i = 0; i < 8; i++){ + children.emplace_back(neg_ones); + point_indices.emplace_back(std::vector()); + centers.emplace_back(translate_center(curr_center,h/2,i)); + widths.emplace_back(h); + } + + + //Split up the points into the corresponding children + for(int j = 0; j < point_indices.at(index).size(); j++){ + IndexType curr_point_index = point_indices.at(index).at(j); + IndexType cell_of_curr_point = + get_octant(P.row(curr_point_index),curr_center)+m; + point_indices.at(cell_of_curr_point).emplace_back(curr_point_index); + } + + //Now increase m + m += 8; + + + // Look ma, I'm calling myself. + for(int i = 0; i < 8; i++){ + helper(children.at(index)(i),depth+1); + } + } + }; + + { + std::vector all(P.rows()); + for(IndexType i = 0;i, int, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::octree, int, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/vendor/igl/octree.h b/src/vendor/igl/octree.h new file mode 100644 index 0000000..a67d181 --- /dev/null +++ b/src/vendor/igl/octree.h @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Gavin Barill +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/ + +#ifndef IGL_OCTREE +#define IGL_OCTREE +#include "igl_inline.h" +#include +#include + + + + +namespace igl +{ + /// Given a set of 3D points P, generate data structures for a pointerless + /// octree. Each cell stores its points, children, center location and width. + /// Our octree is not dense. We use the following rule: if the current cell + /// has any number of points, it will have all 8 children. A leaf cell will + /// have -1's as its list of child indices. + /// + /// We use a binary numbering of children. Treating the parent cell's center + /// as the origin, we number the octants in the following manner: + /// The first bit is 1 iff the octant's x coordinate is positive + /// The second bit is 1 iff the octant's y coordinate is positive + /// The third bit is 1 iff the octant's z coordinate is positive + /// + /// For example, the octant with negative x, positive y, positive z is: + /// 110 binary = 6 decimal + /// + /// @param[in] P #P by 3 list of point locations + /// @param[out] point_indices a vector of vectors, where the ith entry is a + /// vector of the indices into P that are the ith octree cell's points + /// @param[out] CH #OctreeCells by 8, where the ith row is the indices of the + /// ith octree cell's children + /// @param[out] CN #OctreeCells by 3, where the ith row is a 3d row vector + /// representing the position of the ith cell's center + /// @param[out] W #OctreeCells, a vector where the ith entry is the width of + /// the ith octree cell + template + IGL_INLINE void octree(const Eigen::MatrixBase& P, + std::vector > & point_indices, + Eigen::PlainObjectBase& CH, + Eigen::PlainObjectBase& CN, + Eigen::PlainObjectBase& W); +} + +#ifndef IGL_STATIC_LIBRARY +# include "octree.cpp" +#endif + +#endif + diff --git a/src/vendor/igl/parallel_for.h b/src/vendor/igl/parallel_for.h new file mode 100644 index 0000000..ab4d517 --- /dev/null +++ b/src/vendor/igl/parallel_for.h @@ -0,0 +1,387 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PARALLEL_FOR_H +#define IGL_PARALLEL_FOR_H +#include "igl_inline.h" +#include + +//#warning "Defining IGL_PARALLEL_FOR_FORCE_SERIAL" +//#define IGL_PARALLEL_FOR_FORCE_SERIAL + +namespace igl +{ + /// Functional implementation of a basic, open-mp style, parallel + /// for loop. If the inner block of a for-loop can be rewritten/encapsulated in + /// a single (anonymous/lambda) function call `func` so that the serial code + /// looks like: + /// + /// \code{cpp} + /// for(int i = 0;i + inline bool parallel_for( + const Index loop_size, + const FunctionType & func, + const size_t min_parallel=0); + + /// Functional implementation of an open-mp style, parallel for loop with + /// accumulation. For example, serial code separated into n chunks (each to be + /// parallelized with a thread) might look like: + /// + /// \code{cpp} + /// Eigen::VectorXd S; + /// const auto & prep_func = [&S](int n){ S = Eigen:VectorXd::Zero(n); }; + /// const auto & func = [&X,&S](int i, int t){ S(t) += X(i); }; + /// const auto & accum_func = [&S,&sum](int t){ sum += S(t); }; + /// prep_func(n); + /// for(int i = 0;i= number of threads as only + /// argument + /// @param[in] func function handle taking iteration index i and thread id t as only + /// arguments to compute inner block of for loop I.e. + /// for(int i ...){ func(i,t); } + /// @param[in] accum_func function handle taking thread index as only argument, to be + /// called after all calls of func, e.g., for serial accumulation across + /// all n (potential) threads, see n in description of prep_func. + /// @param[in] min_parallel min size of loop_size such that parallel (non-serial) + /// thread pooling should be attempted {0} + /// @return true iff thread pool was invoked + template< + typename Index, + typename PrepFunctionType, + typename FunctionType, + typename AccumFunctionType + > + inline bool parallel_for( + const Index loop_size, + const PrepFunctionType & prep_func, + const FunctionType & func, + const AccumFunctionType & accum_func, + const size_t min_parallel=0); +} + +// Implementation + +#include "default_num_threads.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Backend selection. Exactly one is active: +// +// IGL_PARALLEL_FOR_FORCE_SERIAL always run serially +// IGL_PARALLEL_FOR_TBB Intel oneTBB (EXPERIMENTAL; needs TBB) +// IGL_PARALLEL_FOR_OPENMP OpenMP (EXPERIMENTAL; needs -fopenmp) +// (none of the above) internal std::thread pool (default) +// +// The experimental backends' headers are included ONLY when their macro is +// defined, so the default build pulls in no TBB/OpenMP dependency whatsoever. +// --------------------------------------------------------------------------- +#if defined(IGL_PARALLEL_FOR_TBB) +# include +# include +#elif defined(IGL_PARALLEL_FOR_OPENMP) +# include +#endif + +namespace igl { +namespace internal +{ + +// Thread-local flag: is the current thread already running inside a +// parallel_for region? Nested parallel_for calls run serially so a fixed pool +// (or backend) is never oversubscribed — this is the fix for the thread +// explosion in issue #2412. Scoped so the flag is restored on exit, meaning a +// worker/thread can be reused for an unrelated (non-nested) region afterwards. +inline bool & parallel_for_in_worker() +{ + static thread_local bool flag = false; + return flag; +} +struct parallel_for_worker_scope +{ + const bool prev; + parallel_for_worker_scope() : prev(parallel_for_in_worker()) + { parallel_for_in_worker() = true; } + ~parallel_for_worker_scope() { parallel_for_in_worker() = prev; } +}; + +#if !defined(IGL_PARALLEL_FOR_FORCE_SERIAL) && \ + !defined(IGL_PARALLEL_FOR_TBB) && \ + !defined(IGL_PARALLEL_FOR_OPENMP) +// A minimal fixed-size worker pool built only on //etc. +// +// Design notes: +// - Lazily created on the first *parallel* region (never on include, and never +// when everything stays under min_parallel / single-threaded), so simply +// linking libigl costs no background threads. +// - Intentionally leaked (heap-allocated, never destroyed): process/library +// teardown must not block joining idle or in-flight workers. Joining threads +// from a static destructor is a classic source of shutdown hangs (DLL/plugin +// unload) and is problematic on thread-restricted platforms (WASM, iPadOS — +// the platform in #2412). The OS reclaims the blocked workers at exit. +// - Size is taken from igl::default_num_threads(), so IGL_NUM_THREADS (and +// friends) let an embedder cap or disable pooling. +class parallel_for_pool +{ +public: + // Created once; `nthreads` on later calls is ignored. + static parallel_for_pool & get(const size_t nthreads) + { + static parallel_for_pool * instance = new parallel_for_pool(nthreads); + return *instance; // never deleted (see class comment) + } + size_t size() const { return m_workers.size(); } + void enqueue(std::function task) + { + { + std::lock_guard lock(m_mutex); + m_tasks.push(std::move(task)); + } + m_cv.notify_one(); + } +private: + explicit parallel_for_pool(const size_t nthreads) + { + const size_t n = std::max(1, nthreads); + m_workers.reserve(n); + for(size_t i = 0; i < n; ++i) + { + m_workers.emplace_back([this]() + { + for(;;) + { + std::function task; + { + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [this]{ return !m_tasks.empty(); }); + task = std::move(m_tasks.front()); + m_tasks.pop(); + } + task(); + } + }); + } + } + std::vector m_workers; + std::queue> m_tasks; + std::mutex m_mutex; + std::condition_variable m_cv; +}; +#endif + +} // namespace internal +} // namespace igl + +template +inline bool igl::parallel_for( + const Index loop_size, + const FunctionType & func, + const size_t min_parallel) +{ + // no-op preparation/accumulation + const auto & no_op = [](const size_t /*n_or_t*/){}; + // two-parameter wrapper ignoring thread id + const auto & wrapper = [&func](Index i, size_t /*t*/){ func(i); }; + return parallel_for(loop_size, no_op, wrapper, no_op, min_parallel); +} + + +template< + typename Index, + typename PreFunctionType, + typename FunctionType, + typename AccumFunctionType> +inline bool igl::parallel_for( + const Index loop_size, + const PreFunctionType & prep_func, + const FunctionType & func, + const AccumFunctionType & accum_func, + const size_t min_parallel) +{ + assert(loop_size >= 0); + if(loop_size == 0) return false; + +#ifdef IGL_PARALLEL_FOR_FORCE_SERIAL + // Forced serial: this is the only path compiled, so no backend/pool code + // (nor any TBB/OpenMP dependency) is referenced. + prep_func(1); + for(Index i = 0;i(min_parallel) || + nthreads <= 1 || + igl::internal::parallel_for_in_worker()) + { + prep_func(1); + for(Index i = 0;i(std::min(loop_size, static_cast(nthreads))); + const Index base = loop_size / static_cast(jobs); + const Index rem = loop_size % static_cast(jobs); + tbb::task_arena arena(static_cast(nthreads)); + arena.execute([&]() + { + tbb::parallel_for(size_t(0), jobs, [&](const size_t t) + { + igl::internal::parallel_for_worker_scope scope; + const Index s = + static_cast(t)*base + std::min(static_cast(t),rem); + const Index e = s + base + (static_cast(t) < rem ? 1 : 0); + for(Index k = s; k < e; k++){ func(k,t); } + }); + }); + for(size_t t = 0;t(nthreads)) + { + igl::internal::parallel_for_worker_scope scope; + const size_t t = static_cast(omp_get_thread_num()); + #pragma omp for schedule(static) + for(Index i = 0;i(1, pool.size()); + // prep is called with the number of thread-id slots [0,P) that func/accum see. + prep_func(P); + + // Partition [0,loop_size) into `jobs` contiguous chunks; chunk t is handled by + // exactly one thread, so func(...,t)/accum(t) need no per-slot synchronization. + const size_t jobs = + static_cast(std::min(loop_size, static_cast(P))); + const Index base = loop_size / static_cast(jobs); + const Index rem = loop_size % static_cast(jobs); + const auto chunk_begin = [&](const size_t t) -> Index + { + return static_cast(t)*base + std::min(static_cast(t),rem); + }; + const auto chunk_end = [&](const size_t t) -> Index + { + return chunk_begin(t) + base + (static_cast(t) < rem ? 1 : 0); + }; + + // Completion is tracked by a counter guarded by `done_mutex` (not a lone + // atomic): decrementing, testing-for-zero, and notifying all happen under the + // lock, and the waiter tests the same counter under the lock. This makes the + // last worker's notify and the caller's wake mutually exclusive, so the caller + // cannot wake (even spuriously) and destroy these stack-local primitives while + // a worker is still touching them. + size_t remaining = jobs; + std::mutex done_mutex; + std::condition_variable done_cv; + const auto run_chunk = [&](const size_t t) + { + // Mark this thread as a worker so any parallel_for inside func runs serial. + igl::internal::parallel_for_worker_scope scope; + const Index end = chunk_end(t); + for(Index k = chunk_begin(t); k < end; k++){ func(k,t); } + std::lock_guard lock(done_mutex); + if(--remaining == 0){ done_cv.notify_one(); } + }; + + // Enqueue all-but-one chunk to the pool and run the last one on the calling + // thread — so the calling core isn't left idle, and (with nested→serial) the + // total active threads for this call stay ≤ P. The calling thread blocks below + // until all chunks finish, so capturing the local state by reference is safe. + for(size_t t = 0; t + 1 < jobs; t++) + { + pool.enqueue([&run_chunk,t]{ run_chunk(t); }); + } + run_chunk(jobs-1); + + { + std::unique_lock lock(done_mutex); + done_cv.wait(lock, [&remaining]{ return remaining == 0; }); + } + + for(size_t t = 0;t Date: Thu, 17 Sep 2026 17:05:08 +0100 Subject: [PATCH 4/7] Add libigl fast winding-number backend c_fast_mesh_winding_number() builds a bounding-volume hierarchy once over the mesh (Barill et al. 2018) and evaluates each query point in O(log F), scaling to millions of points on large meshes. Query points are distributed with RcppThread (natcpp's threads convention) rather than libigl's own std::thread pool, so the core count stays controllable for CRAN's check-farm limit. Each worker calls the underlying UT_SolidAngle::computeSolidAngle primitive directly -- the same call libigl's batch overload makes internally. Do NOT loop libigl's templated fast_winding_number(bvh, accuracy, p) wrapper per point instead: that convenience overload is only explicitly instantiated for dynamic float matrices and carries heavy per-call overhead, making it ~450x slower than the primitive (1.82M points on the CA1 mesh: 89s vs 1.6s single-threaded). We also avoid libigl's batch API, whose internal parallel_for reads a process-wide thread-count singleton that cannot honour a per-call `threads` argument. Kept internal (unexported) alongside the internal c_fast_pointsinside() R wrapper: this accelerated path is intended as the back end for nat::pointsinside, while the brute-force winding number remains as an O(P*F) correctness reference. --- NEWS.md | 6 +++ R/RcppExports.R | 4 ++ R/fast_inside_mesh.R | 23 ++++++++++++ src/RcppExports.cpp | 17 +++++++++ src/fast_inside_mesh.cpp | 79 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 R/fast_inside_mesh.R create mode 100644 src/fast_inside_mesh.cpp diff --git a/NEWS.md b/NEWS.md index 0567165..94f1aaa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,12 @@ does not depend on surface normals, so it avoids the spurious "outside point classified as inside" results that normal-based tests can give near thin protrusions or sharp features. Parallelised over points with RcppThread. +* add an internal libigl-accelerated back end (`c_fast_mesh_winding_number()`, + "Fast Winding Numbers for Soups and Clouds", Barill et al. 2018) that builds a + bounding-volume hierarchy once and evaluates each query point in O(log F), + scaling to millions of points on large meshes. Results match the brute-force + method. libigl (MPL-2.0) is vendored under `src/vendor/igl`; requires + `RcppEigen`. # natcpp 0.3.1 diff --git a/R/RcppExports.R b/R/RcppExports.R index 161eb6e..6ea5d55 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -57,6 +57,10 @@ c_coords21dindex <- function(xyz, origin, voxdims, dims, clamp = FALSE) { .Call(`_natcpp_c_coords21dindex`, xyz, origin, voxdims, dims, clamp) } +c_fast_mesh_winding_number <- function(points, vertices, faces, threads = 4L, accuracy = 2.0) { + .Call(`_natcpp_c_fast_mesh_winding_number`, points, vertices, faces, threads, accuracy) +} + c_mesh_winding_number <- function(points, vertices, faces, threads = 4L) { .Call(`_natcpp_c_mesh_winding_number`, points, vertices, faces, threads) } diff --git a/R/fast_inside_mesh.R b/R/fast_inside_mesh.R new file mode 100644 index 0000000..ecfee5a --- /dev/null +++ b/R/fast_inside_mesh.R @@ -0,0 +1,23 @@ +#' Fast point-in-mesh test via libigl fast winding number +#' +#' Accelerated back end for point-in-mesh classification, using libigl's "Fast +#' Winding Numbers for Soups and Clouds" (Barill et al. 2018): a bounding-volume +#' hierarchy is built once over the mesh and each query point is evaluated in +#' \eqn{O(\log F)}, so it scales to millions of points on meshes of tens of +#' thousands of faces. Results match the brute-force [c_mesh_winding_number()] +#' (which is retained as an \eqn{O(P \times F)} correctness reference). +#' +#' @inheritParams c_pointsinside +#' @param accuracy libigl accuracy-scale parameter (default 2). +#' @return For `c_fast_pointsinside`, a logical vector (`TRUE` = inside). For +#' `c_fast_mesh_winding_number`, the approximate winding number per point. +#' @rdname c_fast_pointsinside +#' @keywords internal +#' @noRd +c_fast_pointsinside <- function(points, vertices, faces, threads = 4L, + accuracy = 2) { + w <- c_fast_mesh_winding_number(as.matrix(points), as.matrix(vertices), + matrix(as.integer(faces), ncol = 3L), + threads = threads, accuracy = accuracy) + abs(w) > 0.5 +} diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 6a981d0..001c12f 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -1,6 +1,7 @@ // Generated by using Rcpp::compileAttributes() -> do not edit by hand // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 +#include #include #include @@ -81,6 +82,21 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// c_fast_mesh_winding_number +NumericVector c_fast_mesh_winding_number(NumericMatrix points, NumericMatrix vertices, IntegerMatrix faces, int threads, double accuracy); +RcppExport SEXP _natcpp_c_fast_mesh_winding_number(SEXP pointsSEXP, SEXP verticesSEXP, SEXP facesSEXP, SEXP threadsSEXP, SEXP accuracySEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< NumericMatrix >::type points(pointsSEXP); + Rcpp::traits::input_parameter< NumericMatrix >::type vertices(verticesSEXP); + Rcpp::traits::input_parameter< IntegerMatrix >::type faces(facesSEXP); + Rcpp::traits::input_parameter< int >::type threads(threadsSEXP); + Rcpp::traits::input_parameter< double >::type accuracy(accuracySEXP); + rcpp_result_gen = Rcpp::wrap(c_fast_mesh_winding_number(points, vertices, faces, threads, accuracy)); + return rcpp_result_gen; +END_RCPP +} // c_mesh_winding_number NumericVector c_mesh_winding_number(NumericMatrix points, NumericMatrix vertices, IntegerMatrix faces, int threads); RcppExport SEXP _natcpp_c_mesh_winding_number(SEXP pointsSEXP, SEXP verticesSEXP, SEXP facesSEXP, SEXP threadsSEXP) { @@ -187,6 +203,7 @@ static const R_CallMethodDef CallEntries[] = { {"_natcpp_c_ijkpos", (DL_FUNC) &_natcpp_c_ijkpos, 5}, {"_natcpp_c_sub2ind", (DL_FUNC) &_natcpp_c_sub2ind, 2}, {"_natcpp_c_coords21dindex", (DL_FUNC) &_natcpp_c_coords21dindex, 5}, + {"_natcpp_c_fast_mesh_winding_number", (DL_FUNC) &_natcpp_c_fast_mesh_winding_number, 5}, {"_natcpp_c_mesh_winding_number", (DL_FUNC) &_natcpp_c_mesh_winding_number, 4}, {"_natcpp_c_ListofMatrixRows", (DL_FUNC) &_natcpp_c_ListofMatrixRows, 1}, {"_natcpp_c_listlengths", (DL_FUNC) &_natcpp_c_listlengths, 1}, diff --git a/src/fast_inside_mesh.cpp b/src/fast_inside_mesh.cpp new file mode 100644 index 0000000..7beba39 --- /dev/null +++ b/src/fast_inside_mesh.cpp @@ -0,0 +1,79 @@ +// Fast generalised winding number via libigl's "Fast Winding Numbers for Soups +// and Clouds" (Barill et al. 2018). A bounding-volume hierarchy is built once +// over the mesh, then each query point is evaluated in O(log F). This is the +// accelerated back end intended for large point sets on large meshes; the +// brute-force c_mesh_winding_number() is the O(P*F) reference. +// +// We call libigl's per-point primitive (UT_SolidAngle::computeSolidAngle, the +// same call libigl's batch overload makes internally) directly inside an +// RcppThread::parallelFor. This keeps the core count controllable via the +// `threads` argument -- so CRAN's check-farm limit can be respected -- while +// distributing points across threads ourselves rather than via libigl's own +// std::thread pool (whose size is fixed by a process-global singleton and so +// cannot honour a per-call thread count). Routing each query through libigl's +// templated fast_winding_number(bvh, acc, p) wrapper instead is dramatically +// slower, so we avoid it. +// +// [[Rcpp::depends(RcppEigen)]] +#include +#include +#include +#include "vendor/igl/fast_winding_number.h" + +using namespace Rcpp; +typedef igl::FastWindingNumber::HDK_Sample::UT_Vector3T UTVec3f; + +// [[Rcpp::export]] +NumericVector c_fast_mesh_winding_number(NumericMatrix points, + NumericMatrix vertices, + IntegerMatrix faces, + int threads = 4, + double accuracy = 2.0) { + const int np = points.nrow(); + const int nv = vertices.nrow(); + const int nf = faces.nrow(); + if (points.ncol() != 3) stop("points must be an Nx3 matrix"); + if (vertices.ncol() != 3) stop("vertices must be an Nx3 matrix"); + if (faces.ncol() != 3) stop("faces must be an Nx3 matrix"); + + Eigen::MatrixXd V(nv, 3); + for (int i = 0; i < nv; ++i) { + V(i, 0) = vertices(i, 0); V(i, 1) = vertices(i, 1); V(i, 2) = vertices(i, 2); + } + Eigen::MatrixXi F(nf, 3); + for (int i = 0; i < nf; ++i) { + const int a = faces(i, 0) - 1, b = faces(i, 1) - 1, c = faces(i, 2) - 1; + if (a < 0 || b < 0 || c < 0 || a >= nv || b >= nv || c >= nv) + stop("faces contains a vertex index outside [1, nrow(vertices)]"); + F(i, 0) = a; F(i, 1) = b; F(i, 2) = c; + } + + // Precompute the BVH once (Taylor expansion order 2, as in libigl examples). + igl::FastWindingNumberBVH bvh; + igl::fast_winding_number(V, F, 2, bvh); + + // Copy query points into a plain buffer so the worker threads touch no R data + // structures and no Eigen expression templates. + std::vector Q(static_cast(np) * 3); + for (int i = 0; i < np; ++i) { + Q[3 * i + 0] = static_cast(points(i, 0)); + Q[3 * i + 1] = static_cast(points(i, 1)); + Q[3 * i + 2] = static_cast(points(i, 2)); + } + + NumericVector out(np); + double* out_ptr = out.begin(); // NumericVector is not thread-safe + const float acc = static_cast(accuracy); + const size_t nThreads = (threads > 0) ? static_cast(threads) + : std::thread::hardware_concurrency(); + + // computeSolidAngle is a const, read-only query on the shared BVH, so + // concurrent single-point evaluation is thread-safe. + RcppThread::parallelFor(0, np, [&](int i) { + UTVec3f p; + p[0] = Q[3 * i + 0]; p[1] = Q[3 * i + 1]; p[2] = Q[3 * i + 2]; + out_ptr[i] = bvh.ut_solid_angle.computeSolidAngle(p, acc) / (4.0 * igl::PI); + }, nThreads); + + return out; +} From 0928338ecadf425dfa23f58df148a42229f134b4 Mon Sep 17 00:00:00 2001 From: Gregory Jefferis Date: Thu, 17 Sep 2026 17:05:08 +0100 Subject: [PATCH 5/7] Test libigl fast winding number against brute force and oracle Fast path agrees with the brute-force winding number on the analytic tetrahedron, calls the four known CA1 false positives outside, and matches the independent CGAL oracle on the 2000-point bbox sample. Threads capped at 2. --- tests/testthat/test-inside-mesh.R | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/testthat/test-inside-mesh.R b/tests/testthat/test-inside-mesh.R index 1fd7740..330c227 100644 --- a/tests/testthat/test-inside-mesh.R +++ b/tests/testthat/test-inside-mesh.R @@ -73,3 +73,28 @@ test_that("real CA1 mesh: false positives outside, matches reference", { expect_equal(c_pointsinside(P, d$vertices, d$faces, threads = 2L), d$inside_ref) }) + +test_that("fast (libigl) winding number agrees with brute force and oracle", { + # analytic tetrahedron + m <- tetra() + p <- rbind(c(0.2, 0.2, 0.2), c(0.1, 0.1, 0.1), c(2, 2, 2), c(0.6, 0.6, 0.6)) + expect_equal(c_fast_pointsinside(p, m$V, m$F, threads = 2L), + c_pointsinside(p, m$V, m$F, threads = 2L)) + + f <- test_path("testdata", "ca1_mesh.rds") + skip_if_not(file.exists(f)) + d <- readRDS(f) + + # the known false positives are outside by the fast method too + expect_false(any(c_fast_pointsinside(d$false_positives, d$vertices, d$faces, + threads = 2L))) + + # fast method matches the independent CGAL oracle on the bbox sample + set.seed(d$seed) + bb <- apply(d$vertices, 2, range) + P <- cbind(runif(d$n, bb[1, 1], bb[2, 1]), + runif(d$n, bb[1, 2], bb[2, 2]), + runif(d$n, bb[1, 3], bb[2, 3])) + expect_equal(c_fast_pointsinside(P, d$vertices, d$faces, threads = 2L), + d$inside_ref) +}) From 084255946461f9ec607f5adf391a863405f62c7c Mon Sep 17 00:00:00 2001 From: Gregory Jefferis Date: Thu, 17 Sep 2026 23:02:47 +0100 Subject: [PATCH 6/7] Mark vendored HDK header as a system header to keep CRAN install clean FastWindingNumberForSoups.h is an upstream Side Effects HDK amalgamation (vendored via libigl). Under GCC/Clang it emits -Wpedantic (anonymous structs) and -Wclass-memaccess warnings that CRAN's win-builder r-devel flags as significant, tripping an install-time "R CMD check" WARNING. Add a single `#pragma GCC system_header` at the top so both compilers treat its diagnostics as toolchain-header diagnostics and stay silent, without altering any semantics and without non-portable -Wno-* flags in Makevars. The file is otherwise byte-identical upstream. --- NEWS.md | 4 +++- src/vendor/igl/FastWindingNumberForSoups.h | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 94f1aaa..4705b90 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,7 +15,9 @@ bounding-volume hierarchy once and evaluates each query point in O(log F), scaling to millions of points on large meshes. Results match the brute-force method. libigl (MPL-2.0) is vendored under `src/vendor/igl`; requires - `RcppEigen`. + `RcppEigen`. The bundled Houdini HDK amalgamation is marked a system header + (one-line `#pragma`) so its third-party compiler warnings do not surface as + install-time `R CMD check` warnings. # natcpp 0.3.1 diff --git a/src/vendor/igl/FastWindingNumberForSoups.h b/src/vendor/igl/FastWindingNumberForSoups.h index 304ef13..30ab948 100644 --- a/src/vendor/igl/FastWindingNumberForSoups.h +++ b/src/vendor/igl/FastWindingNumberForSoups.h @@ -1,3 +1,12 @@ +// NATCPP: the single line below is the only local modification to this file, +// which is otherwise an unmodified upstream Side Effects HDK amalgamation +// (vendored via libigl). Marking it a system header makes GCC and Clang treat +// its diagnostics as they would a toolchain header, silencing warnings from +// this third-party code (e.g. -Wpedantic anonymous structs, -Wclass-memaccess) +// without altering any semantics. This keeps `R CMD check` install-time clean +// on CRAN without adding non-portable -Wno-* flags to Makevars. +#pragma GCC system_header + // This header created by issuing: `echo "// This header created by issuing: \`$BASH_COMMAND\` $(echo "" | cat - LICENSE README.md | sed -e "s#^..*#\/\/ &#") $(echo "" | cat - SYS_Types.h SYS_Math.h VM_SSEFunc.h VM_SIMDFunc.h VM_SIMD.h UT_Array.h UT_ArrayImpl.h UT_SmallArray.h UT_FixedVector.h UT_ParallelUtil.h UT_BVH.h UT_BVHImpl.h UT_SolidAngle.h UT_Array.cpp UT_SolidAngle.cpp | sed -e "s/^#.*include *\".*$//g")" > ~/Repos/libigl/include/igl/FastWindingNumberForSoups.h` // MIT License From bb404943ee76654989c4067ad0325a74ecdb16db Mon Sep 17 00:00:00 2001 From: Gregory Jefferis Date: Fri, 18 Sep 2026 06:19:30 +0100 Subject: [PATCH 7/7] Unify point-in-mesh into a single c_pointsinside(method=) Collapse the separate brute-force export and internal libigl wrapper into one exported c_pointsinside() with method = c("auto", "bvh", "bruteforce"). "auto" builds the libigl BVH only for large meshes (>=1000 faces with a non-trivial query workload) and uses the brute-force O(P*F) test otherwise, so a simple mesh such as a cuboid always takes the cheaper path; the choice is asymmetric-safe since a mistaken "bvh" only costs the bounded octree build while a mistaken "bruteforce" on a large mesh costs seconds. Adopt the package threads=NULL policy (natcpp_threads()) in place of the hard-coded threads=4L, and drop the redundant internal c_fast_pointsinside() wrapper. The two C++ back ends (c_mesh_winding_number, c_fast_mesh_winding_number) remain internal .Call targets behind the wrapper. Co-Authored-By: Claude Opus 4.8 --- NEWS.md | 22 +++++---- R/fast_inside_mesh.R | 23 ---------- R/inside_mesh.R | 74 +++++++++++++++++++++++-------- man/c_pointsinside.Rd | 52 +++++++++++++++------- tests/testthat/test-inside-mesh.R | 22 ++++----- 5 files changed, 117 insertions(+), 76 deletions(-) delete mode 100644 R/fast_inside_mesh.R diff --git a/NEWS.md b/NEWS.md index 4705b90..c22e819 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,15 +9,19 @@ (solid-angle) winding number. Unlike a closest-point signed-distance test it does not depend on surface normals, so it avoids the spurious "outside point classified as inside" results that normal-based tests can give near thin - protrusions or sharp features. Parallelised over points with RcppThread. -* add an internal libigl-accelerated back end (`c_fast_mesh_winding_number()`, - "Fast Winding Numbers for Soups and Clouds", Barill et al. 2018) that builds a - bounding-volume hierarchy once and evaluates each query point in O(log F), - scaling to millions of points on large meshes. Results match the brute-force - method. libigl (MPL-2.0) is vendored under `src/vendor/igl`; requires - `RcppEigen`. The bundled Houdini HDK amalgamation is marked a system header - (one-line `#pragma`) so its third-party compiler warnings do not surface as - install-time `R CMD check` warnings. + protrusions or sharp features. `threads = NULL` applies the package thread + policy. +* `c_pointsinside(method=)` selects the winding-number back end: `"bruteforce"` + is a self-contained O(P*F) test parallelised over points with RcppThread; + `"bvh"` uses libigl's "Fast Winding Numbers for Soups and Clouds" (Barill et + al. 2018), building a bounding-volume hierarchy once and evaluating each query + point in O(log F) so it scales to millions of points on large meshes. The + default `"auto"` picks `"bvh"` for large meshes and `"bruteforce"` otherwise; + the two agree to within the winding-number tolerance. libigl (MPL-2.0) is + vendored under `src/vendor/igl` and requires `RcppEigen`; its bundled Houdini + HDK amalgamation is marked a system header (one-line `#pragma`) so its + third-party compiler warnings do not surface as install-time `R CMD check` + warnings. # natcpp 0.3.1 diff --git a/R/fast_inside_mesh.R b/R/fast_inside_mesh.R deleted file mode 100644 index ecfee5a..0000000 --- a/R/fast_inside_mesh.R +++ /dev/null @@ -1,23 +0,0 @@ -#' Fast point-in-mesh test via libigl fast winding number -#' -#' Accelerated back end for point-in-mesh classification, using libigl's "Fast -#' Winding Numbers for Soups and Clouds" (Barill et al. 2018): a bounding-volume -#' hierarchy is built once over the mesh and each query point is evaluated in -#' \eqn{O(\log F)}, so it scales to millions of points on meshes of tens of -#' thousands of faces. Results match the brute-force [c_mesh_winding_number()] -#' (which is retained as an \eqn{O(P \times F)} correctness reference). -#' -#' @inheritParams c_pointsinside -#' @param accuracy libigl accuracy-scale parameter (default 2). -#' @return For `c_fast_pointsinside`, a logical vector (`TRUE` = inside). For -#' `c_fast_mesh_winding_number`, the approximate winding number per point. -#' @rdname c_fast_pointsinside -#' @keywords internal -#' @noRd -c_fast_pointsinside <- function(points, vertices, faces, threads = 4L, - accuracy = 2) { - w <- c_fast_mesh_winding_number(as.matrix(points), as.matrix(vertices), - matrix(as.integer(faces), ncol = 3L), - threads = threads, accuracy = accuracy) - abs(w) > 0.5 -} diff --git a/R/inside_mesh.R b/R/inside_mesh.R index a967666..21620ff 100644 --- a/R/inside_mesh.R +++ b/R/inside_mesh.R @@ -10,36 +10,72 @@ #' thin protrusions or sharp features. #' #' @details The mesh should be closed (watertight) and triangular; the result is -#' independent of face orientation (winding). This is a self-contained \eqn{O(P -#' \times F)} implementation (P points, F faces), parallelised over points with -#' \pkg{RcppThread}; it is intended as the accelerated back end for -#' \code{nat::pointsinside()}. For very large meshes combined with very large -#' point sets a spatially accelerated method (BVH / fast winding number) would -#' be faster. +#' independent of face orientation (winding). It is intended as the +#' accelerated back end for \code{nat::pointsinside()}. +#' +#' Two back ends are available, selected by \code{method}: +#' \describe{ +#' \item{\code{"bruteforce"}}{A self-contained \eqn{O(P \times F)} +#' implementation (P points, F faces), parallelised over points with +#' \pkg{RcppThread}. No setup cost, so it is fastest for small meshes.} +#' \item{\code{"bvh"}}{libigl's "Fast Winding Numbers for Soups and Clouds" +#' (Barill et al. 2018): a bounding-volume hierarchy is built once over +#' the mesh and each query point is then evaluated in \eqn{O(\log F)}, so +#' it scales to millions of points on meshes of tens of thousands of +#' faces. \code{accuracy} tunes the multipole approximation.} +#' } +#' \code{"auto"} (the default) picks \code{"bvh"} only for large meshes queried +#' by enough points to amortise building the hierarchy, and \code{"bruteforce"} +#' otherwise (so a simple mesh such as a cuboid always uses brute force). The +#' two back ends agree to within the winding-number tolerance. #' #' @param points An Nx3 matrix of query point coordinates (or anything #' coercible with \code{as.matrix}). #' @param vertices An Nx3 matrix of mesh vertex coordinates. #' @param faces An Nx3 integer matrix of 1-based vertex indices (one triangle #' per row), e.g. \code{t(mesh$it)} for an \pkg{rgl} \code{mesh3d}. -#' @param threads Number of threads to use (default \code{4}, matching the rest -#' of \pkg{natcpp}). Set to \code{0} to use all available cores. Keep it at or -#' below 2 in package examples and tests to respect CRAN's core limit. -#' @return For \code{c_pointsinside}, a logical vector of length \code{nrow(points)} -#' (\code{TRUE} = inside). For \code{c_mesh_winding_number}, the numeric -#' winding number for each point. +#' @param method Winding-number back end: \code{"auto"} (default), \code{"bvh"} +#' or \code{"bruteforce"}. See \strong{Details}. +#' @param threads Number of threads for parallel computation. The default +#' \code{NULL} applies the package thread policy (respecting +#' \code{getOption("Ncpus")} and the \code{OMP_THREAD_LIMIT} environment +#' variable, else 2). Set to 0 to use all available cores. +#' @param accuracy libigl accuracy-scale parameter for \code{method = "bvh"} +#' (default 2); ignored by the brute-force back end. +#' @return A logical vector of length \code{nrow(points)} (\code{TRUE} = inside). #' @export -#' @rdname c_pointsinside #' @examples -#' \dontrun{ #' # tetrahedron #' V <- rbind(c(0,0,0), c(1,0,0), c(0,1,0), c(0,0,1)) #' F <- rbind(c(1,3,2), c(1,2,4), c(1,4,3), c(2,3,4)) #' c_pointsinside(rbind(c(.2,.2,.2), c(2,2,2)), V, F) # TRUE FALSE -#' } -c_pointsinside <- function(points, vertices, faces, threads = 4L) { - w <- c_mesh_winding_number(as.matrix(points), as.matrix(vertices), - matrix(as.integer(faces), ncol = 3L), - threads = threads) +c_pointsinside <- function(points, vertices, faces, + method = c("auto", "bvh", "bruteforce"), + threads = NULL, accuracy = 2) { + method <- match.arg(method) + points <- as.matrix(points) + vertices <- as.matrix(vertices) + faces <- matrix(as.integer(faces), ncol = 3L) + threads <- natcpp_threads(threads) + + if (method == "auto") { + nf <- nrow(faces) + # The libigl BVH has a fixed build cost (~O(F log F)); above ~1000 faces it + # amortises quickly and then far outperforms the brute-force O(P*F) test + # (measured ~4x at 7k faces, ~25x at 50k). A mistaken "bvh" choice only ever + # costs that bounded build, whereas a mistaken "bruteforce" on a large mesh + # costs seconds, so we lean towards bvh once the mesh is non-trivial -- + # except for tiny total workloads, where brute force is instant and needs no + # build (a cuboid, F=12, always lands in brute force). + method <- if (nf >= 1000L && as.double(nrow(points)) * nf >= 1e6) + "bvh" else "bruteforce" + } + + w <- if (method == "bvh") + c_fast_mesh_winding_number(points, vertices, faces, threads = threads, + accuracy = accuracy) + else + c_mesh_winding_number(points, vertices, faces, threads = threads) + abs(w) > 0.5 } diff --git a/man/c_pointsinside.Rd b/man/c_pointsinside.Rd index ff4f290..e0ecd06 100644 --- a/man/c_pointsinside.Rd +++ b/man/c_pointsinside.Rd @@ -4,7 +4,14 @@ \alias{c_pointsinside} \title{Test which points lie inside a triangle mesh (generalised winding number)} \usage{ -c_pointsinside(points, vertices, faces, threads = 4L) +c_pointsinside( + points, + vertices, + faces, + method = c("auto", "bvh", "bruteforce"), + threads = NULL, + accuracy = 2 +) } \arguments{ \item{points}{An Nx3 matrix of query point coordinates (or anything @@ -15,14 +22,19 @@ coercible with \code{as.matrix}).} \item{faces}{An Nx3 integer matrix of 1-based vertex indices (one triangle per row), e.g. \code{t(mesh$it)} for an \pkg{rgl} \code{mesh3d}.} -\item{threads}{Number of threads to use (default \code{4}, matching the rest -of \pkg{natcpp}). Set to \code{0} to use all available cores. Keep it at or -below 2 in package examples and tests to respect CRAN's core limit.} +\item{method}{Winding-number back end: \code{"auto"} (default), \code{"bvh"} +or \code{"bruteforce"}. See \strong{Details}.} + +\item{threads}{Number of threads for parallel computation. The default +\code{NULL} applies the package thread policy (respecting +\code{getOption("Ncpus")} and the \code{OMP_THREAD_LIMIT} environment +variable, else 2). Set to 0 to use all available cores.} + +\item{accuracy}{libigl accuracy-scale parameter for \code{method = "bvh"} +(default 2); ignored by the brute-force back end.} } \value{ -For \code{c_pointsinside}, a logical vector of length \code{nrow(points)} - (\code{TRUE} = inside). For \code{c_mesh_winding_number}, the numeric - winding number for each point. +A logical vector of length \code{nrow(points)} (\code{TRUE} = inside). } \description{ Robust point-in-mesh test based on the generalised (solid-angle) @@ -36,18 +48,28 @@ Robust point-in-mesh test based on the generalised (solid-angle) } \details{ The mesh should be closed (watertight) and triangular; the result is - independent of face orientation (winding). This is a self-contained \eqn{O(P - \times F)} implementation (P points, F faces), parallelised over points with - \pkg{RcppThread}; it is intended as the accelerated back end for - \code{nat::pointsinside()}. For very large meshes combined with very large - point sets a spatially accelerated method (BVH / fast winding number) would - be faster. + independent of face orientation (winding). It is intended as the + accelerated back end for \code{nat::pointsinside()}. + + Two back ends are available, selected by \code{method}: + \describe{ + \item{\code{"bruteforce"}}{A self-contained \eqn{O(P \times F)} + implementation (P points, F faces), parallelised over points with + \pkg{RcppThread}. No setup cost, so it is fastest for small meshes.} + \item{\code{"bvh"}}{libigl's "Fast Winding Numbers for Soups and Clouds" + (Barill et al. 2018): a bounding-volume hierarchy is built once over + the mesh and each query point is then evaluated in \eqn{O(\log F)}, so + it scales to millions of points on meshes of tens of thousands of + faces. \code{accuracy} tunes the multipole approximation.} + } + \code{"auto"} (the default) picks \code{"bvh"} only for large meshes queried + by enough points to amortise building the hierarchy, and \code{"bruteforce"} + otherwise (so a simple mesh such as a cuboid always uses brute force). The + two back ends agree to within the winding-number tolerance. } \examples{ -\dontrun{ # tetrahedron V <- rbind(c(0,0,0), c(1,0,0), c(0,1,0), c(0,0,1)) F <- rbind(c(1,3,2), c(1,2,4), c(1,4,3), c(2,3,4)) c_pointsinside(rbind(c(.2,.2,.2), c(2,2,2)), V, F) # TRUE FALSE } -} diff --git a/tests/testthat/test-inside-mesh.R b/tests/testthat/test-inside-mesh.R index 330c227..6eee521 100644 --- a/tests/testthat/test-inside-mesh.R +++ b/tests/testthat/test-inside-mesh.R @@ -58,7 +58,7 @@ test_that("real CA1 mesh: false positives outside, matches reference", { # the four points a normal-based test wrongly called inside are all outside expect_false(any(c_pointsinside(d$false_positives, d$vertices, d$faces, - threads = 2L))) + method = "bruteforce", threads = 2L))) expect_equal(c_mesh_winding_number(d$false_positives, d$vertices, d$faces, threads = 2L), rep(0, nrow(d$false_positives)), tolerance = 1e-3) @@ -70,31 +70,33 @@ test_that("real CA1 mesh: false positives outside, matches reference", { runif(d$n, bb[1, 2], bb[2, 2]), runif(d$n, bb[1, 3], bb[2, 3])) stopifnot(identical(dim(P), dim(d$points))) # reproducible sample - expect_equal(c_pointsinside(P, d$vertices, d$faces, threads = 2L), + expect_equal(c_pointsinside(P, d$vertices, d$faces, + method = "bruteforce", threads = 2L), d$inside_ref) }) -test_that("fast (libigl) winding number agrees with brute force and oracle", { +test_that("bvh (libigl) back end agrees with brute force and oracle", { # analytic tetrahedron m <- tetra() p <- rbind(c(0.2, 0.2, 0.2), c(0.1, 0.1, 0.1), c(2, 2, 2), c(0.6, 0.6, 0.6)) - expect_equal(c_fast_pointsinside(p, m$V, m$F, threads = 2L), - c_pointsinside(p, m$V, m$F, threads = 2L)) + expect_equal(c_pointsinside(p, m$V, m$F, method = "bvh", threads = 2L), + c_pointsinside(p, m$V, m$F, method = "bruteforce", threads = 2L)) f <- test_path("testdata", "ca1_mesh.rds") skip_if_not(file.exists(f)) d <- readRDS(f) - # the known false positives are outside by the fast method too - expect_false(any(c_fast_pointsinside(d$false_positives, d$vertices, d$faces, - threads = 2L))) + # the known false positives are outside by the bvh method too + expect_false(any(c_pointsinside(d$false_positives, d$vertices, d$faces, + method = "bvh", threads = 2L))) - # fast method matches the independent CGAL oracle on the bbox sample + # bvh method matches the independent CGAL oracle on the bbox sample set.seed(d$seed) bb <- apply(d$vertices, 2, range) P <- cbind(runif(d$n, bb[1, 1], bb[2, 1]), runif(d$n, bb[1, 2], bb[2, 2]), runif(d$n, bb[1, 3], bb[2, 3])) - expect_equal(c_fast_pointsinside(P, d$vertices, d$faces, threads = 2L), + expect_equal(c_pointsinside(P, d$vertices, d$faces, + method = "bvh", threads = 2L), d$inside_ref) })