https://github.com/mnmly/swiftigl
Swift bindings for libigl — mesh I/O, signed distance, marching cubes, decimation, and more via header-only xcframework
https://github.com/mnmly/swiftigl
cxx-interop eigen geometry-processing libigl macos marching-cubes mesh signed-distance-field swift-package
Last synced: 28 days ago
JSON representation
Swift bindings for libigl — mesh I/O, signed distance, marching cubes, decimation, and more via header-only xcframework
- Host: GitHub
- URL: https://github.com/mnmly/swiftigl
- Owner: mnmly
- License: mit
- Created: 2026-05-16T19:13:38.000Z (29 days ago)
- Default Branch: main
- Last Pushed: 2026-05-16T20:28:38.000Z (29 days ago)
- Last Synced: 2026-05-16T21:35:17.191Z (29 days ago)
- Topics: cxx-interop, eigen, geometry-processing, libigl, macos, marching-cubes, mesh, signed-distance-field, swift-package
- Language: Swift
- Size: 47.9 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# SwiftIGL
[](https://github.com/mnmly/SwiftIGL/actions/workflows/ci.yml)
[](https://mnmly.github.io/SwiftIGL/documentation/swiftigl/)
[](https://swift.org)
[](https://github.com/mnmly/SwiftIGL)
[](LICENSE)
Swift bindings for [libigl](https://libigl.github.io) — a small C++
geometry-processing library — via a header-only xcframework and Swift's
C++ interop.
## Install
Add to `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/mnmly/SwiftIGL", from: "0.1.0"),
],
targets: [
.target(
name: "YourTarget",
dependencies: [.product(name: "SwiftIGL", package: "SwiftIGL")],
swiftSettings: [.interoperabilityMode(.Cxx)] // contagious from SwiftIGL
),
]
```
> **Note:** C++ interop is contagious — any Swift target that imports
> `SwiftIGL` must also set `.interoperabilityMode(.Cxx)`.
## Quick start
```swift
import SwiftIGL
// Load a mesh
let mesh = try readTriangleMesh(at: url)
// Voxelize it
let bb = try boundingBox(mesh, pad: 0.05)
let grid = try voxelGrid(
bboxMin: bb[vertex: 0], bboxMax: bb[vertex: 6],
largestSide: 128
)
// Sample SDF on the grid
let sdf = try signedDistance(
of: grid.points, on: mesh, type: .fastWindingNumber
)
// Re-extract as a clean mesh
let recon = try marchingCubes(scalars: sdf.distances, grid: grid)
try writeTriangleMesh(recon, to: outURL)
```
Full API: see the [DocC reference](https://mnmly.github.io/SwiftIGL/documentation/swiftigl/).
### End-to-end demo executable
```sh
swift run MeshToSDF input.obj reconstructed.obj 128 0.05
```
Reads the mesh → builds a 128³ voxel grid covering its padded bbox → samples
the SDF via fast winding number → extracts the iso-surface with marching
cubes → writes it. Source in `Sources/MeshToSDF/main.swift`.
## Current surface (v0.1)
- **Mesh I/O** — `readTriangleMesh`, `writeTriangleMesh` (`.obj`, `.off`, `.ply`, `.stl`, `.mesh`, `.wrl`)
- **Normals** — `perVertexNormals`, `perFaceNormals`
- **Signed distance / inside-outside** — `signedDistance(of:on:type:)`, `windingNumber(of:on:)`
- **Voxelization** — `voxelGrid`, `marchingCubes`
- **Mesh processing** — `boundingBox`, `decimate`, `removeDuplicateVertices`, `uniqueSimplices`
Modules currently **out of scope**: `copyleft/cgal` (booleans), `tetgen`,
`embree`, `predicates`, `restricted/*`. Open an issue if you need any.
## Requirements
- macOS 13+
- Swift 6.0 / Xcode 16+
- C++17
## How it's built
```
libigl-xcframework-builder → igl.xcframework → SwiftIGL
(sibling repo, shell) (header-only (Swift API
produces stub-lib + xcframework, + thin C++
headers xcframework) released via bridge)
GH Releases)
```
- **`igl` binaryTarget** — library-form xcframework: a stub `.a` plus
`Headers/{igl, Eigen, unsupported}/`. Library-form (not framework-form)
so both `` and `` resolve in C++ TUs.
- **`CxxIGL` target** — hand-written C++ bridge. Owns every `#include`
of libigl/Eigen. Narrow surface (no Eigen types, no exceptions, only
`std::string` + `std::vector` across the boundary).
- **`SwiftIGL` target** — idiomatic Swift API. C++ interop is enabled
here only; consumers' Swift code uses the Swift API and never sees a
C++ type.
Why **header-only library-form** xcframework: libigl consumers need
both `` and `` resolvable in C++ TUs. A
single-prefix framework module cannot offer both; a library+headers
xcframework can.
## Local development
By default the package resolves `igl.xcframework` from a GitHub Release.
To iterate against a locally-built xcframework, swap the binaryTarget
in `Package.swift`:
```swift
.binaryTarget(name: "igl", path: "Frameworks/igl.xcframework")
```
Rebuild with:
```sh
cd ../../cpp/libigl-xcframework-builder
make LIBIGL_VERSION=2.5.0
```
The builder mirrors into `SwiftIGL/Frameworks/` when
`SWIFT_PACKAGE_FRAMEWORKS_DIR` is set in its `config.sh`. `Frameworks/`
is gitignored.
## Build & test
```sh
swift build
swift test
```
## Design notes
- **No Eigen types cross the Swift boundary.** Eigen's expression
templates and >3 generic parameters fall outside Swift's C++ interop
surface. The bridge wraps caller buffers with `Eigen::Map<>`
internally and copies results back into flat `std::vector`s.
- **No exceptions cross the boundary.** Every bridge entry point
returns `bool`; on failure it writes a message to a trailing
`std::string&`. Swift rethrows as `IGLError`.
- **Flat row-major storage** (`3 * V` doubles, `3 * F` int32_t).
Matches typical GPU/raw-buffer layouts (Metal, MLX, nvdiffrast) —
no transpose at the boundary; no per-row allocation.
## License
[MIT](LICENSE) for the SwiftIGL bindings. The bundled libigl + Eigen
headers remain under their original [MPL2](THIRD_PARTY_LICENSES.md)
licenses.