Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 19 additions & 22 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,37 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-14, macos-13]
os: [ubuntu-latest, macos-14, macos-13]

if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')"
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

# - name: Install Boost
# if: runner.os == 'macOS'
# shell: bash
# run: |
# brew upgrade
# brew install boost
# - if: runner.os == 'Linux'
# run: |
# sudo apt-get -y update
# sudo apt-get -y install libboost-dev
# - if: runner.os == 'Windows'
# run: |
# git clone --depth=1 --recurse-submodules -j10 https://github.com/boostorg/boost.git
# cd boost
# bootstrap.bat
- name: Install Boost
if: runner.os == 'macOS'
shell: bash
run: |
brew upgrade
brew install boost
- name: Install Boost
if: runner.os == 'Linux'
run: |
sudo apt-get -y update
sudo apt-get -y install libboost-dev libboost-test-dev libboost-program-options-dev libboost-serialization-dev

- name: ubuntu packages
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y xorg-dev libglu1-mesa-dev xpra xserver-xorg-video-dummy freeglut3-dev

- name: configure
run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DSHM_NO_AMGCL=On
- name: configure (with AMGCL)
run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DSHM_NO_AMGCL=Off

- name: build
- name: build tests
if: runner.os != 'Windows'
run: cd build && make
run: cd test && mkdir build && cd build && cmake ..

- if: runner.os == 'Windows'
run: cd build && cmake --build "." -DSHM_NO_AMGCL=On
- name: run tests
if: runner.os != 'Windows'
run: cd test/build && make && bin/shm_test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export/
build/
**/build/
**/*.ppm
**/site_build/
docs/docs

# Editor and OS things
.DS_Store
Expand Down
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,3 @@
[submodule "deps/amgcl"]
path = deps/amgcl
url = https://github.com/nzfeng/amgcl.git
[submodule "deps/polyscope"]
path = deps/polyscope
url = https://github.com/nmwsharp/polyscope.git
136 changes: 45 additions & 91 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,104 +1,58 @@
cmake_minimum_required(VERSION 3.12.0)
cmake_minimum_required(VERSION 3.14.0)

project(signed-heat-3d)

### Configure output locations
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules)
### Policy settings
cmake_policy(SET CMP0054 NEW) # don't implicitly dereference inside if()

# Print the build type
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release" FORCE)
endif()
message(STATUS "cmake build type: ${CMAKE_BUILD_TYPE}")

### Configure the compiler
# This is a basic, decent setup that should do something sane on most compilers

if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")

# using Clang (linux or apple) or GCC
message("Using clang/gcc compiler flags")
SET(CMAKE_CXX_STANDARD 11)
SET(BASE_CXX_FLAGS "-std=c++11 -Wall -Wextra")
SET(DISABLED_WARNINGS " -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-deprecated-declarations -Wno-missing-braces -Wno-unused-label -Wno-register -Wno-sign-compare -Wno-unknown-pragmas -Wno-unused-result -Wno-narrowing -Wno-unused-but-set-variable -Wno-unused-lambda-capture -Wno-unused-local-typedefs")
SET(TRACE_INCLUDES " -H -Wno-error=unused-command-line-argument")

if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
message("Setting clang-specific options")
SET(BASE_CXX_FLAGS "${BASE_CXX_FLAGS} -ferror-limit=3 -Wfatal-errors -fcolor-diagnostics")
SET(CMAKE_CXX_FLAGS_DEBUG "-g3 -fsanitize=address -fno-limit-debug-info")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
message("Setting gcc-specific options")
SET(BASE_CXX_FLAGS "${BASE_CXX_FLAGS} -fmax-errors=5")
SET(CMAKE_CXX_FLAGS_DEBUG "-g3")
SET(DISABLED_WARNINGS "${DISABLED_WARNINGS} -Wno-maybe-uninitialized -Wno-format-zero-length -Wno-unused-but-set-parameter -Wno-unused-but-set-variable")
endif()

SET(CMAKE_CXX_FLAGS "${BASE_CXX_FLAGS} ${DISABLED_WARNINGS} -std=c++11")

include(CheckCXXCompilerFlag)
unset(COMPILER_SUPPORTS_MARCH_NATIVE CACHE)
CHECK_CXX_COMPILER_FLAG(-march=native COMPILER_SUPPORTS_MARCH_NATIVE)
if(COMPILER_SUPPORTS_MARCH_NATIVE)
SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -march=native -DNDEBUG -std=c++11")
else()
SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -mcpu=apple-m1 -DNDEBUG -std=c++11") # Apple M1
endif()

elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# using Visual Studio C++
message("Using Visual Studio compiler flags")
set(BASE_CXX_FLAGS "${BASE_CXX_FLAGS} /W4")
set(BASE_CXX_FLAGS "${BASE_CXX_FLAGS} /MP") # parallel build
SET(DISABLED_WARNINGS "${DISABLED_WARNINGS} /wd\"4267\"") # ignore conversion to smaller type (fires more aggressively than the gcc version, which is annoying)
SET(DISABLED_WARNINGS "${DISABLED_WARNINGS} /wd\"4244\"") # ignore conversion to smaller type (fires more aggressively than the gcc version, which is annoying)
SET(DISABLED_WARNINGS "${DISABLED_WARNINGS} /wd\"4305\"") # ignore truncation on initialization
SET(CMAKE_CXX_FLAGS "${BASE_CXX_FLAGS} ${DISABLED_WARNINGS}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd")

add_definitions(/D "_CRT_SECURE_NO_WARNINGS")
add_definitions(-DNOMINMAX)
add_definitions(-D_USE_MATH_DEFINES)
### Process settings
option(BUILD_SHARED_LIBS "Build the shared library" FALSE)
if(BUILD_SHARED_LIBS)
message("-- Building SHARED libraries")
else()
# unrecognized
message( FATAL_ERROR "Unrecognized compiler [${CMAKE_CXX_COMPILER_ID}]" )
message("-- Building STATIC libraries")
endif()

# == Deps
add_subdirectory(deps/geometry-central)
add_subdirectory(deps/polyscope)
add_subdirectory(deps/tetgen)
add_subdirectory(deps/amgcl)

# == Build our project stuff

set(SRCS
src/signed_heat_tet_solver.cpp
src/signed_heat_grid_solver.cpp
src/signed_heat_3d.cpp
src/main.cpp
)

option(SHM_NO_AMGCL "Do not use AMGCL" OFF)
if (SHM_NO_AMGCL)
add_definitions(-DSHM_NO_AMGCL=)
message(STATUS "Not using AMGCL")
else()
add_definitions(-USHM_NO_AMGCL)
message(STATUS "Using AMGCL")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") # look for stuff in the /cmake directory

# Work with non-standard homebrew installations
# (from ceres build system)
if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
find_program(HOMEBREW_EXECUTABLE brew)
mark_as_advanced(FORCE HOMEBREW_EXECUTABLE)
if (HOMEBREW_EXECUTABLE)
# Detected a Homebrew install, query for its install prefix.
execute_process(COMMAND ${HOMEBREW_EXECUTABLE} --prefix
OUTPUT_VARIABLE HOMEBREW_INSTALL_PREFIX
OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "Detected Homebrew with install prefix: "
"${HOMEBREW_INSTALL_PREFIX}, adding to CMake search paths.")
list(APPEND CMAKE_PREFIX_PATH "${HOMEBREW_INSTALL_PREFIX}")
endif()
endif()

add_executable(main "${SRCS}")
target_include_directories(main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/")
target_include_directories(main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/tetgen/")

# add the args.hxx project which we use for command line args
target_include_directories(main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/polyscope/deps/args")
target_link_libraries(main geometry-central polyscope tetgen amgcl::amgcl)
### Handle windows-specific fixes
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
add_definitions (-DNOMINMAX) # don't use weird windows built-in min/max
add_definitions (-D_USE_MATH_DEFINES) # match unix behavior of constants in cmath
endif()

# Add libIGL in header-only mode.
target_include_directories(main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/libigl/include")
### Recurse to the source code
add_subdirectory(src)

# install
install(
TARGETS signed-heat-3d
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib)

install(
DIRECTORY ${CMAKE_SOURCE_DIR}/include/
DESTINATION include
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.ipp")
91 changes: 25 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
# signed-heat-3d (3D volumetric domains)

## Documentation is at [nzfeng.github.io/signed-heat-3d](https://nzfeng.github.io/signed-heat-3d)

C++ demo for "[A Heat Method for Generalized Signed Distance](https://nzfeng.github.io/research/SignedHeatMethod/index.html)" by [Nicole Feng](https://nzfeng.github.io/index.html) and [Keenan Crane](https://www.cs.cmu.edu/~kmcrane/), presented at SIGGRAPH 2024.

<!-- Documentation: -->
This library implements the _Signed Heat Method (SHM)_ on **3D volumetric domains**, solving for (generalized) signed distance to triangle meshes, polygon meshes, and point clouds. No assumptions are placed on the input, besides that it be consistently oriented.

<!-- Python bindings: -->
![teaser image](media/teaser.png)

<!-- Unit tests -->
Check out the [sample project](https://github.com/nzfeng/signed-heat-demo-3d) to get started with a build system and a GUI.

Project page with links to paper, pseudocode, supplementals, & videos: [link](https://nzfeng.github.io/research/SignedHeatMethod/index.html)
Check out the docs at [nzfeng.github.io/signed-heat-3d](nzfeng.github.io/signed-heat-3d).

This Github repository demonstrates the _Signed Heat Method (SHM)_ on **3D volumetric domains**, solving for (generalized) signed distance to triangle meshes, polygon meshes, and point clouds. No assumptions are placed on the input, besides that it be consistently oriented.
More resources:
* Python bindings have been released as the `signed-heat-method` package on PyPI. Checkout the [documentation](nzfeng.github.io/signed-heat-3d) for install instructions.
* If you're interested in using the Signed Heat Method in 2D surface domains, go to [this Github repository](https://github.com/nzfeng/signed-heat-demo) which demonstrates the [geometry-central implementation on (2D) surface meshes and point clouds](https://geometry-central.net/surface/algorithms/signed_heat_method/).
* Project page with links to paper, pseudocode, supplementals, & videos: [link](https://nzfeng.github.io/research/SignedHeatMethod/index.html)

For visualization, the solution is solved on a background tet mesh or grid, which you do _not_ need to supply yourself -- the program discretizes the domain for you, and you just need to supply the geometry to which you'd like the signed distance.
## Performance

If you're interested in using the Signed Heat Method in 2D surface domains, go to [this Github repository](https://github.com/nzfeng/signed-heat-demo) which demonstrates the [geometry-central implementation on (2D) surface meshes and point clouds](https://geometry-central.net/surface/algorithms/signed_heat_method/) .
1. To improve performance, operators and spatial discretizations are only built as necessary, and re-used in future computations if the underlying discretization hasn't changed. This means future computations can be significantly faster than the initial solve (which includes, for example, tet mesh construction and matrix factorization.)

![teaser image](media/teaser.png)
2. Linear solves are (optionally) accelerated using the algebraic multigrid library [AMGCL](https://amgcl.readthedocs.io/en/latest/), which (unfortunately) requires Boost. If you do not want to use Boost, use `cmake -DSHM_NO_AMGCL=On` to compile to a program without AMGCL but with solve times \~5x slower (more or less for larger/smaller problems). Force use of AMGCL via `cmake -DSHM_NO_AMGCL=Off`. Boost can be installed on macOS using `brew install boost`, and the necessary modules on Ubuntu using
```
sudo apt-get -y update
sudo apt-get -y install libboost-dev libboost-test-dev libboost-program-options-dev libboost-serialization-dev
```
Windows users should probably follow the instructions on the [Boost website](https://www.boost.org/releases/latest/).

3. There are still several further obvious areas of performance improvement, which haven't been implemented yet:
* In 3D domains, Step 1 of the Signed Heat Method (vector diffusion) can be done by convolution; the integral is evaluted simply by direct summation, even though this summation is trivially parallelizable.
* One could optimize loop order when iterating over source/domain elements (whichever is smaller) for better cache behavior.
* More performance-critical implementations could also implement hierarchical summation.

## Citation

If this code contributes to academic work, please cite as:
```bibtex
Expand All @@ -38,61 +55,3 @@ If this code contributes to academic work, please cite as:
numpages = {19}
}
```

## Getting started

This project uses [geometry-central](https://geometry-central.net) for mesh computations, [Tetgen](https://www.wias-berlin.de/software/tetgen/1.5/index.html) for tet mesh generation, [Polyscope](http://polyscope.run/) for visualization, and [libigl](https://libigl.github.io/) for a marching tets implementation. These dependencies are added as git submodules, so copies will be downloaded locally when you clone this project as below.

```
git clone --recursive https://github.com/nzfeng/signed-heat-3d.git
cd signed-heat-3d
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release .. # use `Debug` mode to enable checks
make -j8 # or however many cores you want to use
bin/main /path/to/mesh
```
A Polyscope GUI will open.

If you do not clone recursively, some submodules or sub-submodules will not clone. Initialize/update these submodules by running `git submodule update --init --recursive` or `git submodule update --recursive`.

Linear solves are accelerated using the algebraic multigrid library [AMGCL](https://amgcl.readthedocs.io/en/latest/), which (unfortunately) requires Boost. Boost can be installed on macOS using `brew install boost`; Windows and Linux users should probably follow the instructions on the [Boost website](https://www.boost.org/releases/latest/). If you do not want to use Boost, use `cmake -DSHM_NO_AMGCL=On` to compile to a program without AMGCL but with solve times \~5x slower (more or less for larger/smaller problems). Force use of AMGCL via `cmake -DSHM_NO_AMGCL=Off`.

# Mesh & point cloud input

## File formats
An input mesh may be an `obj`, `ply`, `off`, or `stl`. See [the geometry-central website](https://geometry-central.net/surface/utilities/io/#reading-meshes) for up-to-date information on supported file types.

An input point cloud should be specified using point positions and normals. Some example files, with extension `.pc`, are in the `data` directory.

The algorithm is robust to self-intersections, holes, and noise in the input geometry, and to a certain amount of inconsistent normal orientations.

# Usage

<!-- Full documentation lives at [](). -->

In addition to the mesh file, you can pass several arguments to the command line, including flags which are also shown as options in the GUI.

|flag | usage | purpose|
| ------------- |-------------|-------------|
|`--g`, `--grid`| `--g`, `--grid` | Solve on a background grid. By default, the domain will be discretized as a tet mesh. |
|`--V`, `--verbose`| `--V`, `--verbose`| Verbose output. Off by default.|
|`--h`| `--h=64`, `--h=64,64,128` `--h 64, 32, 128`| 3D vector specifying the tet/grid spacing, with larger values indicating more refinement. If solving on a grid, this corresponds to the number of nodes along each dimension. Default values are $2^{5}$.|
|`--b`| `--b=0., 0., 0., 1., 1., 1.`, `--b 0., 0., 0., 1., 1., 1.`| Specify the 3D positions of the minimum and maximum corners of the computational domain (in that order), which is assumed to be an axis-aligned rectangular prism. If not specified, the size of the domain will be automatically computed so as to encompass the input source geometry.|
|`--l`, `--headless`| `--l`, `--headless`| Don't use the GUI, and automatically solve for & export the generalized SDF.|
|`--help`| `--help`| Display help. |

To improve performance, operators and spatial discretizations are only built as necessary, and re-used in future computations if the underlying discretization hasn't changed. This means future computations can be significantly faster than the initial solve (which includes, for example, tet mesh construction and matrix factorization.)

# Performance

Linear solves are accelerated using the algebraic multigrid implementation in [AMGCL](https://amgcl.readthedocs.io/en/latest/).

But there are still several further obvious areas of performance improvement, which haven't been implemented yet:

* In 3D domains, Step 1 of the Signed Heat Method (vector diffusion) can be done by convolution; the integral is evaluted simply by direct summation, even though this summation is trivially parallelizable.
* One could optimize loop order when iterating over source/domain elements (whichever is smaller) for better cache behavior.
* More performance-critical implementations could also implement hierarchical summation.

# Output

Polyscope lets you inspect your solution on the interior of the domain by adding [_slice planes_](https://polyscope.run/features/slice_planes/). In this program, you can also contour your solution, and export isosurfaces as OBJ files.
Loading