-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Hi @DanielVandH, I absolute love this package well done!. I think it is vital for geometry processing etc and for scattered data interpolation/extrapolation.
I've tested this package and it works really well when interpolating well within the convexhull. And when I say "well within" I suppose that means for regions with a lot of points surrounding them rather than at the boundary.
Here is a screenshot of the performance of the various methods where I test it very much like your demo in the README but with non-gridded (random) data point input:

It looks like at the boundary we snap to a kind of "nearest neighbour/linear extrapolation origami". There are clear creases and kinks and it seems that this doesn't just happen outside of the convex hull but also just at the boundary (see the kinks starting inside the convex hull e.g. at the top corner). I suspect there are two things going on here. 1) you probably switch method for extrapolation, and 2) perhaps there are to few points at the boundary to enforce the smoothness. Would it help to reflect points out at the boundary so the local slopes continue and additional fictitious data is provided. Or is there another way, especially for the smoother methods, to make the behaviour at the boundaries more consistent with the otherwise C1/ C2 continuous nature of them?
naturalNeighbourInterp.mp4
Thanks!
For completeness, here is the code I used:
using GLMakie
using GLMakie.GeometryBasics
using NaturalNeighbours
using StableRNGs
f = (x, y) -> sin(x * y) - cos(x - y) * exp(-(x - y)^2)
numPointsData = 100
rng = StableRNG(123)
x = rand(rng, numPointsData)
y = rand(rng, numPointsData)
z = f.(x,y)
itp = interpolate(x,y, z; derivatives=true)
numPointsInterp = 100
x_i_r = range(0.0, 1.0, numPointsInterp)
y_i_r = range(0.0, 1.0, numPointsInterp)
function pointGrid(x_i_r, y_i_r)
x_i = Vector{Float64}(undef,numPointsInterp^2)
y_i = Vector{Float64}(undef,numPointsInterp^2)
i = 1
for y in y_i_r
for x in x_i_r
x_i[i] = x
y_i[i] = y
i+=1
end
end
return x_i, y_i
end
x_i, y_i = pointGrid(x_i_r, y_i_r)
methodSet = (NaturalNeighbours.Sibson(),
NaturalNeighbours.Triangle(),
NaturalNeighbours.Laplace(),
NaturalNeighbours.Sibson(1),
NaturalNeighbours.Nearest(),
NaturalNeighbours.Farin(),
NaturalNeighbours.Hiyoshi(1),
NaturalNeighbours.Hiyoshi(2)
)
titleStrings = ("Sibson", "Triangle", "Laplace", "Sibson(1)", "Nearest", "Farin", "Hiyoshi(1)", "Hiyoshi(2)")
fig = Figure(size = (800,800))
V = [Point{3,Float64}(x[i], y[i], z[i]) for i in eachindex(x)]
function toquadmesh(V, numRows)
numCols = length(V)/numRows # Number of point "columns"
if !isinteger(numCols) || numCols<1
throw(ArgumentError("The length(V)/numRows should produce an integer >1 but is instead $numCols"))
end
numCols = Int(numCols)
ij2ind(i,j) = i + numCols*(j-1) # function to convert subscript to linear indices
F = Vector{QuadFace{Int}}(undef,(numCols-1)*(numRows-1))
i_f = 1
@inbounds for i = 1:(numCols-1)
@inbounds for j = 1:(numRows-1)
F[i_f] = QuadFace{Int}(ij2ind(i,j),
ij2ind(mod1(i+1,numCols), j),
ij2ind(mod1(i+1,numCols), mod1(j+1,numRows)),
ij2ind(i,mod1(j+1,numRows)))
i_f += 1
end
end
return F
end
for testCase = 1:1:length(methodSet)
methodNow = methodSet[testCase]
z_i = itp(x_i,y_i; method=methodNow)
V_i = [Point{3,Float64}(x_i[i], y_i[i], z_i[i]) for i in eachindex(x_i)]
if testCase<=4
r = 1
c = testCase
else
r = 2
c = testCase-4
end
F_i = toquadmesh(V_i, numPointsInterp)
ax = Axis3(fig[r, c], clip=false, viewmode = :free, aspect = :data, xlabel = "X", ylabel = "Y", zlabel = "Z", title="method: " * titleStrings[testCase])
hp1 = scatter!(ax, V, markersize=12, color=:black)
# hp2 = scatter!(ax, V_i, markersize=8, color=:red)
hp3 = mesh!(ax, GLMakie.GeometryBasics.Mesh(V_i,F_i), color=z_i)
end
fig