This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
WhatsThePoint.jl is a Julia package providing tools for manipulating point clouds for use in the solution of PDEs via meshless methods. It is part of the JuliaMeshless organization.
Important: This package is under heavy development and does not yet have robust testing for all methods.
# Activate package environment
] activate .
] instantiate
# Run full test suite
julia --project -e 'using Pkg; Pkg.test()'
# Build documentation locally
julia --project=docs docs/make.jlTests use TestItemRunner.jl with @testitem macros — each test item is self-contained and runs independently. There is no way to run a single test file in isolation; @run_package_tests discovers and runs all @testitem blocks.
WhatsThePoint.jl uses a hierarchical type system for representing point clouds:
- PointSurface - Points with normals and areas representing a surface
- PointBoundary - Collection of named surfaces forming a boundary
- PointVolume - Interior volume points
- PointCloud - Combines boundary and volume into a complete point cloud (mutable)
All types inherit from Domain{M,C} where M<:Manifold and C<:CRS (coordinate reference system) from the Meshes.jl ecosystem. This parameterization enables type stability and proper dispatch.
- Composition over inheritance: Types build on each other (surfaces → boundary → cloud)
- Immutability: PointSurface is immutable; PointCloud is mutable for iterative construction
- Mutation convention: Geometry creation/transformation is functional (returns new objects). Metadata reorganization and derived-quantity recomputation (
split_surface!,orient_normals!,rebuild_topology!) mutates in-place, indicated by!. - StructArrays: Surface elements stored as StructArray for cache-friendly memory layout
- Heavy parallelization: Uses OhMyThreads (tmap, tmapreduce) throughout
- Full unit support: Unitful.jl integrated across all operations
surface.jl- PointSurface and SurfaceElement typesboundary.jl- PointBoundary managing named surfacesvolume.jl- PointVolume for interior pointscloud.jl- PointCloud combining boundary, volume, and topologytopology.jl- Point connectivity (KNNTopology, RadiusTopology)
normals.jl- Normal computation using PCA and orientation via MST+DFS (Hoppe 1992)isinside.jl- Point-in-polygon/volume tests (2D: winding number, 3D: Green's function)shadow.jl- Shadow point generationsurface_operations.jl- Split, combine, add surfaces to boundaries
spatial_octree.jl- Core octree data structure with adaptive subdivision and 2:1 balancingtriangle_octree.jl-TriangleOctreefor mesh-aware spatial queries and leaf classificationspacing_criterion.jl-SpacingCriterionfor spacing-driven octree subdivisiongeometric_utils.jl- Triangle-box intersection teststraits.jl- Octree trait definitions
Four algorithms available with important 2D vs 3D considerations:
- SlakKosec (3D only, default) -
algorithms/slak_kosec.jl - VanDerSandeFornberg (3D only) -
algorithms/vandersande_fornberg.jl - FornbergFlyer (2D only) -
algorithms/fornberg_flyer.jl - Octree (3D only) -
algorithms/octree.jl— dual-octree spacing-driven adaptive fill (this is a discretization algorithm; not to be confused withTriangleOctree, a spatial data structure)
Spacing types in spacings.jl: ConstantSpacing, LogLike, BoundaryLayerSpacing
repel.jl- Node repulsion algorithm (Miotti 2023) for improving point distribution quality
WhatsThePoint.jl currently supports Euclidean manifolds only (𝔼{2} and 𝔼{3}). The following functions have explicit Euclidean type constraints:
compute_normals/orient_normals!- Uses PCA and Euclidean dot productsdiscretizealgorithms - Euclidean point generationrepel- Euclidean distance-based repulsionisinside(Green's function) - Euclidean normsdistance- Explicitly uses Euclidean metricgenerate_shadows- Euclidean vector arithmetic
Coordinate Systems: Any CRS is supported on Euclidean manifolds (Cartesian, Cylindrical, Polar, etc.). The Euclidean requirement is about geometric structure (flat space), not coordinate representation.
Future Extensions: The type system is designed to support non-Euclidean geometries through multiple dispatch. To add spherical/hyperbolic manifolds, implement manifold-specific methods with appropriate geodesic operations.
The discretization algorithms are dimension-specific:
- 2D geometries: Must use
FornbergFlyer() - 3D geometries: Use
SlakKosec()(default),VanDerSandeFornberg(), orOctree()
Normal computation and orientation uses a two-step process (Hoppe 1992):
- Compute normals via PCA on local neighborhoods
- Orient consistently using minimum spanning tree with DFS traversal
This approach handles arbitrary surface topologies without requiring manifold assumptions.
Different algorithms for different dimensions:
- 2D: Winding number algorithm
- 3D: Green's function approach, or
TriangleOctree-accelerated O(1) queries for large meshes
When importing meshes (e.g., STL files), the package uses face centers as boundary points, not vertices. This is important for understanding point distributions after import.
PointCloud, PointSurface, and PointVolume all support optional topology storing point neighborhoods for meshless stencils. All types use immutable structs with functional API (operations return new objects).
# Add k-nearest neighbor topology (returns new cloud)
cloud = set_topology(cloud, KNNTopology, 21)
# Or radius-based topology
cloud = set_topology(cloud, RadiusTopology, 2mm)
# Surface-level topology (local indices)
surf = set_topology(surf, KNNTopology, 10)
neighbors(surf, i) # Local indices: 1..length(surf)
# Volume-level topology (local indices)
vol = set_topology(vol, KNNTopology, 15)
neighbors(vol, i) # Local indices: 1..length(vol)
# Cloud-level topology (global indices)
neighbors(cloud, i) # Global indices: 1..length(cloud)
# Check state
hastopology(cloud) # true if topology existsKey behaviors:
NoTopologyis the default (backwards compatible)- Topology is built eagerly when
set_topologyis called - All operations return new objects (immutable design for AD compatibility)
repelreturns new cloud withNoTopology(points moved)- No invalidation needed - immutable objects can't become stale
Type hierarchy:
AbstractTopology{S}- abstract base with storage type parameterNoTopology- singleton, no connectivityKNNTopology{S}- k-nearest neighborsRadiusTopology{S,R}- radius-based neighbors
Multi-level topology:
- PointSurface has
topology::Tfield for surface-local connectivity - PointVolume has
topology::Tfield for volume-local connectivity - PointCloud has
topology::Tfield for global connectivity - Component topologies use local indices; cloud topology uses global indices
using WhatsThePoint
# Import boundary from STL
boundary = PointBoundary("path/to/file.stl")
# Define spacing
spacing = ConstantSpacing(1m)
# Discretize volume (choose algorithm based on dimensionality)
cloud = discretize(boundary, spacing;
alg=VanDerSandeFornberg(),
max_points=100_000)# Split surfaces at 75 degree angle threshold
split_surface!(boundary, 75°)
# This modifies the boundary in-place, creating new named surfaces
# based on normal discontinuities# Volume-only repulsion (2D or 3D) — only interior points move
cloud = repel(cloud, spacing; β=0.2, max_iters=1000)
# Boundary-aware repulsion (3D only) — all points move, escaped points projected back
octree = TriangleOctree("model.stl"; classify_leaves=true)
cloud = repel(cloud, spacing, octree; β=0.2, max_iters=1000)
# Collect convergence history via keyword
conv = Float64[]
cloud = repel(cloud, spacing, octree; β=0.2, max_iters=1000, convergence=conv)using GLMakie
# Visualize point cloud
visualize(cloud; markersize=0.15)
# Visualize boundary
visualize(boundary; markersize=0.15)discretize- Generate volume points from boundary (returns new cloud)split_surface!- Split boundary surfaces by normal angle thresholdcombine_surfaces!- Merge multiple surfaces into onecompute_normals/orient_normals!- Normal vector handlingrepel- Optimize point distribution via node repulsion; two methods:repel(cloud, spacing)volume-only,repel(cloud, spacing, octree)boundary-projected (returns new cloud)isinside- Test if point is inside domainimport_surface- Load from STL/mesh files (via GeoIO.jl)save- Save to file (:jld2default, or:vtkformat)visualize- Makie-based visualizationset_topology- Build point connectivity and return new objectrebuild_topology!- Rebuild topology in place with same parametersneighbors- Access point neighborhoods from topology
Tests use TestItemRunner.jl with @testitem macros:
test/
├── runtests.jl # Main orchestrator (@run_package_tests)
├── testsetup.jl # Common imports and test data (CommonImports, TestData, OctreeTestData)
├── points.jl # Point utilities tests
├── normals.jl # Normal computation tests
├── surface.jl # PointSurface tests
├── boundary.jl # PointBoundary tests
├── cloud.jl # PointCloud tests
├── volume.jl # PointVolume tests
├── topology.jl # Topology tests (KNNTopology, RadiusTopology)
├── isinside.jl # Point-in-volume tests
├── discretization.jl # Discretization algorithm tests
├── repel.jl # Node repulsion tests
├── shadow.jl # Shadow point tests
├── surface_operations.jl # Split/combine surface tests
├── neighbors.jl # Neighbor query tests
├── metrics.jl # Distribution metrics tests
├── utils.jl # Utility function tests
├── io.jl # Import/export tests
├── indexing.jl # Index-space conversion tests
├── octree.jl # Octree discretization algorithm tests
├── octree_basic.jl # Octree data structure tests
├── octree_discretization.jl # Octree discretization tests
├── octree_geometric.jl # Octree geometry tests
├── octree_isinside.jl # Octree-accelerated isinside tests
├── octree_spacing_criterion.jl # Octree spacing criterion tests
├── octree_triangle_octree.jl # TriangleOctree tests
├── octree_regression_curvature.jl # Octree curvature regression tests
└── data/
├── bifurcation.stl # Test data (24,780 points)
└── box.stl # Test data
.github/workflows/CI.yml runs on Ubuntu, macOS, Windows with Julia 1.10, 1.11, and 1.12. Tests trigger on pushes to main, tags, and PRs (excluding docs/license changes).
Documentation builds automatically via documenter.yml and deploys to GitHub Pages.
snake_casefor functions and variables,CamelCasefor types and modules,SCREAMING_SNAKE_CASEfor constants!suffix for mutating functions (e.g.,normalize!)- No type piracy — don't add methods to types you don't own for functions you don't own
- Type stability: never change a variable's type mid-function; use concrete types in struct fields
constfor global variables, or pass them as function arguments- Use
@viewsfor array slices to avoid allocations - Pre-allocate outputs; prefer in-place operations (
mul!,ldiv!,@.) - Column-major iteration order — inner loop over first index
- No
Vector{Any}or abstract element types in containers - Performance-critical code must live inside functions, never at global scope
- Prefer multiple dispatch over if/else type-checking
- Don't over-constrain argument types — use duck typing (e.g.,
f(x)notf(x::Float64)) unless dispatch requires it - Use parametric types for generic, performant structs
- Abstract types for API/dispatch hierarchies; concrete types for storage
- Use specific exception types (
ArgumentError,DimensionMismatch,BoundsError, etc.) throwfor actual errors, not control flow
- Standard layout:
src/,test/,docs/ - Explicit
export— only export the public API - Triple-quote docstrings (
"""...""") above functions
- Investigate the project's test setup first (
Test,TestItemRunner, etc.) - Use
@test_throwsfor error cases - Each
@testitemis self-contained with its ownusingstatements (TestItems.jl requirement)