Merge pull request #6 from delfrrr/layout-updates
Use mapbox hpp skel
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..cb9f91c
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,19 @@
+Standard: Cpp11
+IndentWidth: 4
+AccessModifierOffset: -4
+UseTab: Never
+BinPackParameters: false
+BinPackArguments: false
+AllowShortIfStatementsOnASingleLine: true
+AllowShortLoopsOnASingleLine: false
+AllowShortBlocksOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowAllParametersOfDeclarationOnNextLine: true
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+AlwaysBreakTemplateDeclarations: true
+NamespaceIndentation: None
+PointerBindsToType: true
+SpacesInParentheses: false
+BreakBeforeBraces: Attach
+ColumnLimit: 0
+Cpp11BracedListStyle: false
diff --git a/.gitignore b/.gitignore
index ff5aa79..7dde577 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,14 +2,16 @@
CMakeFiles
CMakeScripts
Testing
-Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
.vscode
-build
-includes
+cmake-build
.DS_Store
out
-node_modules
\ No newline at end of file
+node_modules
+mason_packages
+.toolchain
+.mason
+local.env
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 76aa4d1..e1a5b82 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,56 @@
-language: cpp
-dist: trusty
-compiler: clang++
-os: linux
+language: generic
+
+matrix:
+ include:
+ # clang-format specific job
+ - os: linux
+ sudo: false
+ env: CLANG_FORMAT
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'libstdc++6', 'libstdc++-5-dev' ]
+ script:
+ - make format
+ - os: linux
+ sudo: false
+ env: CXX=g++-5
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'g++-5' ]
+ - os: linux
+ sudo: false
+ env: CXX=clang++
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'libstdc++6', 'libstdc++-5-dev' ]
+ # disabled before fixing https://github.com/delfrrr/delaunator-cpp/issues/5
+ # - os: linux
+ # sudo: required # workaround https://github.com/mapbox/node-cpp-skel/issues/93
+ # env: CXXFLAGS="-fsanitize=address,undefined,integer -fno-sanitize-recover=all"
+ # addons:
+ # apt:
+ # sources: [ 'ubuntu-toolchain-r-test' ]
+ # packages: [ 'libstdc++6', 'libstdc++-5-dev' ]
+env:
+ global:
+ - CMAKE_VERSION="3.8.2"
+
install:
-- cmake -DCMAKE_BUILD_TYPE=Release
-- VERBOSE=1 make
-script: ./delaunator-test
+ # set up the environment by installing mason and clang++
+ - ./scripts/setup.sh --config local.env
+ # put mason and clang++ on PATH
+ - source local.env
+ - mason install cmake ${CMAKE_VERSION}
+ - mason link cmake ${CMAKE_VERSION}
+ - which cmake
+
+script:
+ - make release
+ - make test
+ - make clean
+ - make debug
+ - make test
+ - make clean
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 58e1ae3..72a83c2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,38 +1,63 @@
-cmake_minimum_required(VERSION 3.0.0)
-project(delaunator VERSION 0.1.0)
+cmake_minimum_required(VERSION 3.8)
+project(delaunator VERSION 0.2.0)
set (CMAKE_CXX_STANDARD 14)
-if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/includes")
- execute_process(COMMAND bash "-c" "(cd ${CMAKE_CURRENT_SOURCE_DIR} && ./fetch-includes.sh)")
+set(CMAKE_CXX_STANDARD_REQUIRED on)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/mason.cmake)
+
+option(WERROR "Add -Werror flag to build (turns warnings into errors)" ON)
+
+# configure optimization
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(OPTIMIZATION_FLAGS "-O0 -DDEBUG")
+ message("-- Configuring debug build")
+else()
+ set(OPTIMIZATION_FLAGS "-O3 -DNDEBUG")
+ message("-- Configuring release build")
endif()
-#delaunator
-add_library(delaunator src/delaunator.cpp)
-target_include_directories (delaunator PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/includes/rapidjson/include")
-target_include_directories (delaunator PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/includes/prettyprint")
+# Enable extra warnings to adhere to https://github.com/mapbox/cpp/issues/37
+set(DESIRED_WARNINGS "-Wall -Wextra -Wconversion -Wunreachable-code -Wuninitialized -pedantic-errors -Wold-style-cast -Wno-error=unused-variable -Wshadow -Wfloat-equal -Weffc++")
+if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+ set(DESIRED_WARNINGS "${DESIRED_WARNINGS} -Wmost")
+endif()
-#delaunator
-add_library(json-helpers src/json-helpers.cpp)
-target_include_directories (json-helpers PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/includes/rapidjson/include")
+# Note: -D_GLIBCXX_USE_CXX11_ABI=0 is needed to support mason packages that are precompiled libs
+# Currently we only depend on a header only library, but this will help avoid issues when more libs are added via mason
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPTIMIZATION_FLAGS} -D_GLIBCXX_USE_CXX11_ABI=0 ${DESIRED_WARNINGS}")
-#delaunator-test
-add_executable(delaunator-test src/delaunator-test.cpp)
-target_link_libraries(delaunator-test delaunator)
-target_include_directories (delaunator-test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/includes/catch/single_include/catch2")
-target_link_libraries(delaunator-test json-helpers)
+if (WERROR)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
+endif()
-#benchmark
-add_executable(benchmark src/benchmark.cpp)
-target_link_libraries(benchmark delaunator)
-target_link_libraries(benchmark json-helpers)
+# mason_use is a mason function within the mason.cmake file and provides ready-to-go vars, like "STATIC_LIBS" and "INCLUDE_DIRS"
+mason_use(catch VERSION 2.4.0 HEADER_ONLY)
+include_directories(SYSTEM ${MASON_PACKAGE_catch_INCLUDE_DIRS})
+
+mason_use(rapidjson VERSION 1.1.0 HEADER_ONLY)
+include_directories(SYSTEM ${MASON_PACKAGE_rapidjson_INCLUDE_DIRS})
+
+mason_use(benchmark VERSION 1.2.0)
+include_directories(SYSTEM ${MASON_PACKAGE_benchmark_INCLUDE_DIRS})
+
+include_directories("${PROJECT_SOURCE_DIR}/include")
+
+file(GLOB TEST_SOURCES test/*.cpp)
+add_executable(unit-tests ${TEST_SOURCES})
+
+# libbenchmark.a supports threads and therefore needs pthread support
+find_package(Threads REQUIRED)
+file(GLOB BENCH_SOURCES bench/*.cpp)
+add_executable(bench-tests ${BENCH_SOURCES})
+
+#examples
+add_executable(triangulate-geojson examples/triangulate_geojson.cpp)
+add_executable(basic examples/basic.cpp)
-#triangulate
-add_executable(triangulate src/triangulate.cpp)
-target_include_directories (triangulate PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/includes/rapidjson/include")
-target_include_directories (triangulate PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/includes/prettyprint")
-target_link_libraries(triangulate delaunator)
-target_link_libraries(triangulate json-helpers)
-
+# link benchmark static library to the bench-tests binary so the bench tests know where to find the benchmark impl code
+target_link_libraries(bench-tests ${MASON_PACKAGE_benchmark_STATIC_LIBS} ${CMAKE_THREAD_LIBS_INIT})
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5abaceb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,43 @@
+
+# Whether to turn compiler warnings into errors
+export WERROR ?= true
+export BUILD_DIR ?= cmake-build
+
+default: release
+
+release:
+ mkdir -p ./$(BUILD_DIR) && cd ./$(BUILD_DIR) && cmake ../ -DCMAKE_BUILD_TYPE=Release -DWERROR=$(WERROR) && VERBOSE=1 cmake --build .
+
+debug:
+ mkdir -p ./$(BUILD_DIR) && cd ./$(BUILD_DIR) && cmake ../ -DCMAKE_BUILD_TYPE=Debug -DWERROR=$(WERROR) && VERBOSE=1 cmake --build .
+
+test:
+ @if [ -f ./$(BUILD_DIR)/unit-tests ]; then ./$(BUILD_DIR)/unit-tests; else echo "Please run 'make release' or 'make debug' first" && exit 1; fi
+
+bench:
+ @if [ -f ./$(BUILD_DIR)/bench-tests ]; then ./$(BUILD_DIR)/bench-tests; else echo "Please run 'make release' or 'make debug' first" && exit 1; fi
+
+tidy:
+ @echo "not implmented"
+
+coverage:
+ @echo "not implmented"
+
+clean:
+ rm -rf ./$(BUILD_DIR)
+ # remove remains from running 'make coverage'
+ rm -f *.profraw
+ rm -f *.profdata
+ @echo "run 'make distclean' to also clear mason_packages, .mason, and .toolchain directories"
+
+distclean: clean
+ rm -rf mason_packages
+ # remove remains from running './scripts/setup.sh'
+ rm -rf .mason
+ rm -rf .toolchain
+ rm -f local.env
+
+format:
+ ./scripts/format.sh
+
+.PHONY: test bench
\ No newline at end of file
diff --git a/README.md b/README.md
index c552418..d45a370 100644
--- a/README.md
+++ b/README.md
@@ -6,45 +6,41 @@
delaunator-cpp is a C++ port from https://github.com/mapbox/delaunator a JavaScript implementation of very fast 2D Delaunay algorithm.
[](https://travis-ci.org/delfrrr/delaunator-cpp)
+[](https://github.com/mapbox/hpp-skel)
## Features
* Probably the fastest C++ open source 2D Delaunay implementation
-* Roughly 3 times faster then JS version.
+* Roughly 6 times faster then JS version (more improvements are coming).
* Example showing triangulation of GeoJson points
## Usage
+`examples/basic.cpp`
+
```CPP
-#include "delaunator.h"
-#include <cstdio>
-using namespace std;
-//...
-int main(int, char* argv[]) {
- //...
- const vector<double> coords = {/* x0, y0, x1, y1, ... */};
+#include <delaunator.hpp>
+#include <cstdio>
+
+int main() {
+ /* x0, y0, x1, y1, ... */
+ std::vector<double> coords = {-1, 1, 1, 1, 1, -1, -1, -1};
//triangulation happens here
- //note moving points to constructor
- Delaunator delaunator(move(coords));
+ delaunator::Delaunator d(coords);
- for(long int i = 0; i < delaunator.triangles.size(); i+=3) {
+ for(std::size_t i = 0; i < d.triangles.size(); i+=3) {
printf(
"Triangle points: [[%f, %f], [%f, %f], [%f, %f]]\n",
- delaunator.coords[2 * delaunator.triangles[i]], //tx0
- delaunator.coords[2 * delaunator.triangles[i] + 1], //ty0
- delaunator.coords[2 * delaunator.triangles[i + 1]], //tx1
- delaunator.coords[2 * delaunator.triangles[i + 1] + 1], //ty1
- delaunator.coords[2 * delaunator.triangles[i + 2]], //tx2
- delaunator.coords[2 * delaunator.triangles[i + 2] + 1], //ty2
- )
+ d.coords[2 * d.triangles[i]], //tx0
+ d.coords[2 * d.triangles[i] + 1], //ty0
+ d.coords[2 * d.triangles[i + 1]], //tx1
+ d.coords[2 * d.triangles[i + 1] + 1],//ty1
+ d.coords[2 * d.triangles[i + 2]], //tx2
+ d.coords[2 * d.triangles[i + 2] + 1] //ty2
+ );
}
}
```
-For full example see `src/triangulate.cpp`
-
-## TODO
-
-* Benchmarks
-* Unit tests
+[See more examples here](./examples)
diff --git a/bench/run.cpp b/bench/run.cpp
new file mode 100644
index 0000000..06ee8f1
--- /dev/null
+++ b/bench/run.cpp
@@ -0,0 +1,19 @@
+#include "../examples/utils.hpp"
+#include <benchmark/benchmark.h>
+#include <delaunator.hpp>
+#include <string>
+
+namespace {
+void BM_45K_geojson_nodes(benchmark::State& state) {
+ std::string points_str = utils::read_file("./test/test-files/osm-nodes-45331-epsg-3857.geojson");
+ std::vector<double> coords = utils::get_geo_json_points(points_str);
+
+ while (state.KeepRunning()) {
+ delaunator::Delaunator delaunator(coords);
+ }
+}
+} // namespace
+
+BENCHMARK(BM_45K_geojson_nodes)->Unit(benchmark::kMillisecond);
+
+BENCHMARK_MAIN()
diff --git a/cmake/mason.cmake b/cmake/mason.cmake
new file mode 100644
index 0000000..31b2312
--- /dev/null
+++ b/cmake/mason.cmake
@@ -0,0 +1,231 @@
+string(RANDOM LENGTH 16 MASON_INVOCATION)
+
+# Directory where Mason packages are located; typically ends with mason_packages
+if (NOT MASON_PACKAGE_DIR)
+ set(MASON_PACKAGE_DIR "${CMAKE_SOURCE_DIR}/mason_packages")
+endif()
+
+# URL prefix of where packages are located.
+if (NOT MASON_REPOSITORY)
+ set(MASON_REPOSITORY "https://mason-binaries.s3.amazonaws.com")
+endif()
+
+# Path to Mason executable
+if (NOT MASON_COMMAND)
+ set(MASON_COMMAND "${CMAKE_SOURCE_DIR}/.mason/mason")
+endif()
+
+# Determine platform
+# we call uname -s manually here since
+# CMAKE_HOST_SYSTEM_NAME will not be defined before the project() call
+execute_process(
+ COMMAND uname -s
+ OUTPUT_VARIABLE UNAME_S
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+if(NOT MASON_PLATFORM)
+ if (UNAME_S STREQUAL "Darwin")
+ set(MASON_PLATFORM "macos")
+ else()
+ set(MASON_PLATFORM "linux")
+ endif()
+endif()
+
+
+# Determine platform version string
+if(MASON_PLATFORM STREQUAL "ios")
+ set(MASON_PLATFORM_VERSION "8.0") # Deployment target version
+elseif(MASON_PLATFORM STREQUAL "android")
+ if (ANDROID_ABI STREQUAL "armeabi")
+ set(MASON_PLATFORM_VERSION "arm-v5-9")
+ elseif(ANDROID_ABI STREQUAL "arm64-v8a")
+ set(MASON_PLATFORM_VERSION "arm-v8-21")
+ elseif(ANDROID_ABI STREQUAL "x86")
+ set(MASON_PLATFORM_VERSION "x86-9")
+ elseif(ANDROID_ABI STREQUAL "x86_64")
+ set(MASON_PLATFORM_VERSION "x86-64-21")
+ elseif(ANDROID_ABI STREQUAL "mips")
+ set(MASON_PLATFORM_VERSION "mips-9")
+ elseif(ANDROID_ABI STREQUAL "mips64")
+ set(MASON_PLATFORM_VERSION "mips64-21")
+ else()
+ set(MASON_PLATFORM_VERSION "arm-v7-9")
+ endif()
+elseif(NOT MASON_PLATFORM_VERSION)
+ execute_process(
+ COMMAND uname -m
+ OUTPUT_VARIABLE MASON_PLATFORM_VERSION
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+endif()
+
+if(MASON_PLATFORM STREQUAL "macos")
+ set(MASON_PLATFORM "osx")
+endif()
+
+set(ENV{MASON_PLATFORM} "${MASON_PLATFORM}")
+set(ENV{MASON_PLATFORM_VERSION} "${MASON_PLATFORM_VERSION}")
+
+include(CMakeParseArguments)
+
+function(mason_use _PACKAGE)
+ if(NOT _PACKAGE)
+ message(FATAL_ERROR "[Mason] No package name given")
+ endif()
+
+ cmake_parse_arguments("" "HEADER_ONLY" "VERSION" "" ${ARGN})
+
+ if(_UNPARSED_ARGUMENTS)
+ message(FATAL_ERROR "[Mason] mason_use() called with unrecognized arguments: ${_UNPARSED_ARGUMENTS}")
+ endif()
+
+ if(NOT _VERSION)
+ message(FATAL_ERROR "[Mason] Specifying a version is required")
+ endif()
+
+ if(MASON_PACKAGE_${_PACKAGE}_INVOCATION STREQUAL "${MASON_INVOCATION}")
+ # Check that the previous invocation of mason_use didn't select another version of this package
+ if(NOT MASON_PACKAGE_${_PACKAGE}_VERSION STREQUAL ${_VERSION})
+ message(FATAL_ERROR "[Mason] Already using ${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_VERSION}. Cannot select version ${_VERSION}.")
+ endif()
+ else()
+ if(_HEADER_ONLY)
+ set(_PLATFORM_ID "headers")
+ else()
+ set(_PLATFORM_ID "${MASON_PLATFORM}-${MASON_PLATFORM_VERSION}")
+ endif()
+
+ set(_SLUG "${_PLATFORM_ID}/${_PACKAGE}/${_VERSION}")
+ set(_INSTALL_PATH "${MASON_PACKAGE_DIR}/${_SLUG}")
+ file(RELATIVE_PATH _INSTALL_PATH_RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${_INSTALL_PATH}")
+
+ if(NOT EXISTS "${_INSTALL_PATH}")
+ set(_CACHE_PATH "${MASON_PACKAGE_DIR}/.binaries/${_SLUG}.tar.gz")
+ if (NOT EXISTS "${_CACHE_PATH}")
+ # Download the package
+ set(_URL "${MASON_REPOSITORY}/${_SLUG}.tar.gz")
+ message(STATUS "[Mason] Downloading package ${_URL}...")
+
+ set(_FAILED)
+ set(_ERROR)
+ # Note: some CMake versions are compiled without SSL support
+ get_filename_component(_CACHE_DIR "${_CACHE_PATH}" DIRECTORY)
+ file(MAKE_DIRECTORY "${_CACHE_DIR}")
+ execute_process(
+ COMMAND curl --retry 3 -s -f -S -L "${_URL}" -o "${_CACHE_PATH}.tmp"
+ RESULT_VARIABLE _FAILED
+ ERROR_VARIABLE _ERROR)
+ if(_FAILED)
+ message(FATAL_ERROR "[Mason] Failed to download ${_URL}: ${_ERROR}")
+ else()
+ # We downloaded to a temporary file to prevent half-finished downloads
+ file(RENAME "${_CACHE_PATH}.tmp" "${_CACHE_PATH}")
+ endif()
+ endif()
+
+ # Unpack the package
+ message(STATUS "[Mason] Unpacking package to ${_INSTALL_PATH_RELATIVE}...")
+ file(MAKE_DIRECTORY "${_INSTALL_PATH}")
+ execute_process(
+ COMMAND ${CMAKE_COMMAND} -E tar xzf "${_CACHE_PATH}"
+ WORKING_DIRECTORY "${_INSTALL_PATH}")
+ endif()
+
+ # Create a config file if it doesn't exist in the package
+ # TODO: remove this once all packages have a mason.ini file
+ if(NOT EXISTS "${_INSTALL_PATH}/mason.ini")
+ # Change pkg-config files
+ file(GLOB_RECURSE _PKGCONFIG_FILES "${_INSTALL_PATH}/*.pc")
+ foreach(_PKGCONFIG_FILE IN ITEMS ${_PKGCONFIG_FILES})
+ file(READ "${_PKGCONFIG_FILE}" _PKGCONFIG_FILE_CONTENT)
+ string(REGEX REPLACE "(^|\n)prefix=[^\n]*" "\\1prefix=${_INSTALL_PATH}" _PKGCONFIG_FILE_CONTENT "${_PKGCONFIG_FILE_CONTENT}")
+ file(WRITE "${_PKGCONFIG_FILE}" "${_PKGCONFIG_FILE_CONTENT}")
+ endforeach()
+
+ if(NOT EXISTS "${MASON_COMMAND}")
+ message(FATAL_ERROR "[Mason] Could not find Mason command at ${MASON_COMMAND}")
+ endif()
+
+ set(_FAILED)
+ set(_ERROR)
+ execute_process(
+ COMMAND ${MASON_COMMAND} config ${_PACKAGE} ${_VERSION}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_FILE "${_INSTALL_PATH}/mason.ini"
+ RESULT_VARIABLE _FAILED
+ ERROR_VARIABLE _ERROR)
+ if(_FAILED)
+ message(FATAL_ERROR "[Mason] Could not get configuration for package ${_PACKAGE} ${_VERSION}: ${_ERROR}")
+ endif()
+ endif()
+
+ set(MASON_PACKAGE_${_PACKAGE}_PREFIX "${_INSTALL_PATH}" CACHE STRING "${_PACKAGE} ${_INSTALL_PATH}" FORCE)
+ mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_PREFIX)
+
+ # Load the configuration from the ini file
+ file(STRINGS "${_INSTALL_PATH}/mason.ini" _CONFIG_FILE)
+ foreach(_LINE IN LISTS _CONFIG_FILE)
+ string(REGEX MATCH "^([a-z_]+) *= *" _KEY "${_LINE}")
+ if (_KEY)
+ string(LENGTH "${_KEY}" _KEY_LENGTH)
+ string(SUBSTRING "${_LINE}" ${_KEY_LENGTH} -1 _VALUE)
+ string(REGEX REPLACE ";.*$" "" _VALUE "${_VALUE}") # Trim trailing commas
+ string(REPLACE "{prefix}" "${_INSTALL_PATH}" _VALUE "${_VALUE}")
+ string(STRIP "${_VALUE}" _VALUE)
+ string(REPLACE "=" "" _KEY "${_KEY}")
+ string(STRIP "${_KEY}" _KEY)
+ string(TOUPPER "${_KEY}" _KEY)
+ if(_KEY STREQUAL "INCLUDE_DIRS" OR _KEY STREQUAL "STATIC_LIBS" )
+ separate_arguments(_VALUE)
+ endif()
+ set(MASON_PACKAGE_${_PACKAGE}_${_KEY} "${_VALUE}" CACHE STRING "${_PACKAGE} ${_KEY}" FORCE)
+ mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_${_KEY})
+ endif()
+ endforeach()
+
+ # Compare version in the package to catch errors early on
+ if(NOT _VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_VERSION)
+ message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has version '${MASON_PACKAGE_${_PACKAGE}_VERSION}', but required '${_VERSION}'")
+ endif()
+
+ if(NOT _PACKAGE STREQUAL MASON_PACKAGE_${_PACKAGE}_NAME)
+ message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has name '${MASON_PACKAGE_${_PACKAGE}_NAME}', but required '${_NAME}'")
+ endif()
+
+ if(NOT _HEADER_ONLY)
+ if(NOT MASON_PLATFORM STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM)
+ message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform '${MASON_PACKAGE_${_PACKAGE}_PLATFORM}', but required '${MASON_PLATFORM}'")
+ endif()
+
+ if(NOT MASON_PLATFORM_VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION)
+ message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform version '${MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION}', but required '${MASON_PLATFORM_VERSION}'")
+ endif()
+ endif()
+
+ # Concatenate the static libs and libraries
+ set(_LIBRARIES)
+ list(APPEND _LIBRARIES ${MASON_PACKAGE_${_PACKAGE}_STATIC_LIBS} ${MASON_PACKAGE_${_PACKAGE}_LDFLAGS})
+ set(MASON_PACKAGE_${_PACKAGE}_LIBRARIES "${_LIBRARIES}" CACHE STRING "${_PACKAGE} _LIBRARIES" FORCE)
+ mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARIES)
+
+ if(NOT _HEADER_ONLY)
+ string(REGEX MATCHALL "(^| +)-L *([^ ]+)" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LDFLAGS}")
+ string(REGEX REPLACE "(^| +)-L *" "\\1" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}")
+ set(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" CACHE STRING "${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" FORCE)
+ mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS)
+ endif()
+
+ # Store invocation ID to prevent different versions of the same package in one invocation
+ set(MASON_PACKAGE_${_PACKAGE}_INVOCATION "${MASON_INVOCATION}" CACHE INTERNAL "${_PACKAGE} invocation ID" FORCE)
+ endif()
+endfunction()
+
+macro(target_add_mason_package _TARGET _VISIBILITY _PACKAGE)
+ if (NOT MASON_PACKAGE_${_PACKAGE}_INVOCATION)
+ message(FATAL_ERROR "[Mason] Package ${_PACKAGE} has not been initialized yet")
+ endif()
+
+ target_include_directories(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_INCLUDE_DIRS}")
+ target_compile_definitions(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_DEFINITIONS}")
+ target_compile_options(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_OPTIONS}")
+ target_link_libraries(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_LIBRARIES}")
+endmacro()
diff --git a/examples/basic.cpp b/examples/basic.cpp
new file mode 100644
index 0000000..276ceb2
--- /dev/null
+++ b/examples/basic.cpp
@@ -0,0 +1,22 @@
+#include <delaunator.hpp>
+#include <cstdio>
+
+int main() {
+ /* x0, y0, x1, y1, ... */
+ std::vector<double> coords = {-1, 1, 1, 1, 1, -1, -1, -1};
+
+ //triangulation happens here
+ delaunator::Delaunator d(coords);
+
+ for(std::size_t i = 0; i < d.triangles.size(); i+=3) {
+ printf(
+ "Triangle points: [[%f, %f], [%f, %f], [%f, %f]]\n",
+ d.coords[2 * d.triangles[i]], //tx0
+ d.coords[2 * d.triangles[i] + 1], //ty0
+ d.coords[2 * d.triangles[i + 1]], //tx1
+ d.coords[2 * d.triangles[i + 1] + 1],//ty1
+ d.coords[2 * d.triangles[i + 2]], //tx2
+ d.coords[2 * d.triangles[i + 2] + 1] //ty2
+ );
+ }
+}
diff --git a/examples/triangulate_geojson.cpp b/examples/triangulate_geojson.cpp
new file mode 100644
index 0000000..4c24850
--- /dev/null
+++ b/examples/triangulate_geojson.cpp
@@ -0,0 +1,80 @@
+#include "rapidjson/document.h"
+#include "rapidjson/prettywriter.h"
+#include <delaunator.hpp>
+#include "./utils.hpp"
+#include <cstdio>
+#include <fstream>
+#include <vector>
+#include <initializer_list>
+#include <iostream>
+
+std::string serialize_to_json(delaunator::Delaunator const& delaunator) {
+ rapidjson::StringBuffer sb;
+ rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(sb);
+ writer.StartObject();
+ writer.String("type"); writer.String("FeatureCollection");
+ writer.String("crs");
+ writer.StartObject();
+ writer.String("type"); writer.String("name");
+ writer.String("properties");
+ writer.StartObject();
+ writer.String("name"); writer.String("urn:ogc:def:crs:EPSG::900913");
+ writer.EndObject();
+ writer.EndObject();
+ writer.String("features");
+ writer.StartArray();
+ for(std::size_t i = 0; i < delaunator.triangles.size(); i+=3) {
+ writer.StartObject();
+ writer.String("type"); writer.String("Feature");
+ writer.String("properties"); writer.StartObject(); writer.EndObject();
+ writer.String("geometry");
+ writer.StartObject();
+ writer.String("type"); writer.String("Polygon");
+ writer.String("coordinates");
+ writer.StartArray();
+ writer.StartArray();
+ writer.StartArray();
+ writer.Double(delaunator.coords[2 * delaunator.triangles[i]]);
+ writer.Double(delaunator.coords[2 * delaunator.triangles[i] + 1]);
+ writer.EndArray();
+ writer.StartArray();
+ writer.Double(delaunator.coords[2 * delaunator.triangles[i + 1]]);
+ writer.Double(delaunator.coords[2 * delaunator.triangles[i + 1] + 1]);
+ writer.EndArray();
+ writer.StartArray();
+ writer.Double(delaunator.coords[2 * delaunator.triangles[i + 2]]);
+ writer.Double(delaunator.coords[2 * delaunator.triangles[i + 2] + 1]);
+ writer.EndArray();
+ writer.StartArray();
+ writer.Double(delaunator.coords[2 * delaunator.triangles[i]]);
+ writer.Double(delaunator.coords[2 * delaunator.triangles[i] + 1]);
+ writer.EndArray();
+ writer.EndArray();
+ writer.EndArray();
+ writer.EndObject();
+ writer.EndObject();
+ }
+ writer.EndArray();
+ writer.EndObject();
+ return std::string(sb.GetString());
+}
+
+int main(int, char* argv[]) {
+ const char* filename = argv[1];
+ const char* output = argv[2];
+ std::string json = utils::read_file(filename);
+ std::vector<double> coords = utils::get_geo_json_points(json);
+ delaunator::Delaunator delaunator(coords);
+ const char* out_json = serialize_to_json(delaunator).c_str();
+
+ if (output) {
+ printf("Writing to file %s", output);
+ std::ofstream stream;
+ stream.open(output);
+ stream << out_json;
+ stream.close();
+ } else {
+ std::puts(out_json);
+ }
+ return 0;
+}
diff --git a/examples/utils.hpp b/examples/utils.hpp
new file mode 100644
index 0000000..fd8c622
--- /dev/null
+++ b/examples/utils.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <fstream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+#include "rapidjson/document.h"
+
+namespace utils {
+
+std::string read_file(const char* filename) {
+ std::ifstream input_file(filename);
+ if(input_file.good()) {
+ std::string json_str(
+ (std::istreambuf_iterator<char>(input_file)),
+ std::istreambuf_iterator<char>()
+ );
+ return json_str;
+ } else {
+ printf("Error reading file %s", filename);
+ throw std::runtime_error("Error reading file");
+ }
+}
+
+std::vector<double> get_geo_json_points(std::string const& json) {
+ rapidjson::Document document;
+ if(document.Parse(json.c_str()).HasParseError()) {
+ throw std::runtime_error("Cannot parse JSON");
+ }
+ const rapidjson::Value& features = document["features"];
+ std::vector<double> coords;
+ // vector<double> y_vector;
+ for(rapidjson::SizeType i = 0; i < features.Size(); i++) {
+ const rapidjson::Value& coordinates = features[i]["geometry"]["coordinates"];
+ const double x = coordinates[0].GetDouble();
+ const double y = coordinates[1].GetDouble();
+ coords.push_back(x);
+ coords.push_back(y);
+ }
+ return coords;
+}
+
+std::vector<double> get_array_points(std::string const& json) {
+ std::vector<double> points;
+ rapidjson::Document document;
+ if(document.Parse(json.c_str()).HasParseError()) {
+ throw std::runtime_error("Cannot parse JSON");
+ }
+ if(!document.IsArray()) {
+ throw std::runtime_error("It's not JSON Array");
+ }
+ points.reserve(static_cast<std::size_t>(document.Size()));
+ for(rapidjson::SizeType i = 0; i < document.Size(); i++) {
+ points.push_back(document[i].GetDouble());
+ }
+ return points;
+
+}
+
+} // end ns utils
diff --git a/fetch-includes.sh b/fetch-includes.sh
deleted file mode 100755
index 46f7adc..0000000
--- a/fetch-includes.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-
-mkdir -p ./includes
-rm -rf ./includes/*
-
-git clone --branch v1.1.0 --depth=1 https://github.com/Tencent/rapidjson.git ./includes/rapidjson
-git clone --branch master --depth=1 https://github.com/louisdx/cxx-prettyprint.git ./includes/prettyprint
-git clone --branch master --depth=1 https://github.com/catchorg/Catch2.git ./includes/catch
diff --git a/generate-reference-triangles/README.md b/generate-reference-triangles/README.md
index 39be18e..42b912a 100644
--- a/generate-reference-triangles/README.md
+++ b/generate-reference-triangles/README.md
@@ -5,5 +5,5 @@
```
npm i
-./index.js ../test-files/playgrounds-1356-epsg-3857.geojson ../test-files/playgrounds-1356-triangles.json
-```
\ No newline at end of file
+./index.js ../test/test-files/playgrounds-1356-epsg-3857.geojson ../test/test-files/playgrounds-1356-triangles.json
+```
diff --git a/generate-reference-triangles/index.js b/generate-reference-triangles/index.js
index 11724f9..9356209 100755
--- a/generate-reference-triangles/index.js
+++ b/generate-reference-triangles/index.js
@@ -26,6 +26,9 @@
console.log('miliseconds =', end - start);
console.log('triangles =', delaunator.triangles.length);
-const trianglesAr = Array.from(delaunator.triangles);
-writeFileSync(outputFile, JSON.stringify(trianglesAr));
+if (outputFile) {
+ console.log('Saving to', outputFile)
+ const trianglesAr = Array.from(delaunator.triangles);
+ writeFileSync(outputFile, JSON.stringify(trianglesAr));
+}
diff --git a/include/delaunator.hpp b/include/delaunator.hpp
new file mode 100644
index 0000000..0a38442
--- /dev/null
+++ b/include/delaunator.hpp
@@ -0,0 +1,513 @@
+#pragma once
+
+#include <algorithm>
+#include <cmath>
+#include <exception>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace delaunator {
+
+inline double dist(
+ const double ax,
+ const double ay,
+ const double bx,
+ const double by) {
+ const double dx = ax - bx;
+ const double dy = ay - by;
+ return dx * dx + dy * dy;
+}
+
+inline double circumradius(
+ const double ax,
+ const double ay,
+ const double bx,
+ const double by,
+ const double cx,
+ const double cy) {
+ const double dx = bx - ax;
+ const double dy = by - ay;
+ const double ex = cx - ax;
+ const double ey = cy - ay;
+
+ const double bl = dx * dx + dy * dy;
+ const double cl = ex * ex + ey * ey;
+ const double d = dx * ey - dy * ex;
+
+ const double x = (ey * bl - dy * cl) * 0.5 / d;
+ const double y = (dx * cl - ex * bl) * 0.5 / d;
+
+ if ((bl > 0.0 || bl < 0.0) && (cl > 0.0 || cl < 0.0) && (d > 0.0 || d < 0.0)) {
+ return x * x + y * y;
+ } else {
+ return std::numeric_limits<double>::max();
+ }
+}
+
+inline double area(
+ const double px,
+ const double py,
+ const double qx,
+ const double qy,
+ const double rx,
+ const double ry) {
+ return (qy - py) * (rx - qx) - (qx - px) * (ry - qy);
+}
+
+inline std::pair<double, double> circumcenter(
+ const double ax,
+ const double ay,
+ const double bx,
+ const double by,
+ const double cx,
+ const double cy) {
+ const double dx = bx - ax;
+ const double dy = by - ay;
+ const double ex = cx - ax;
+ const double ey = cy - ay;
+
+ const double bl = dx * dx + dy * dy;
+ const double cl = ex * ex + ey * ey;
+ const double d = dx * ey - dy * ex;
+
+ const double x = ax + (ey * bl - dy * cl) * 0.5 / d;
+ const double y = ay + (dx * cl - ex * bl) * 0.5 / d;
+
+ return std::make_pair(x, y);
+}
+
+inline double compare(
+ std::vector<double> const& coords,
+ std::size_t i,
+ std::size_t j,
+ double cx,
+ double cy) {
+ const double d1 = dist(coords[2 * i], coords[2 * i + 1], cx, cy);
+ const double d2 = dist(coords[2 * j], coords[2 * j + 1], cx, cy);
+ const double diff1 = d1 - d2;
+ const double diff2 = coords[2 * i] - coords[2 * j];
+ const double diff3 = coords[2 * i + 1] - coords[2 * j + 1];
+
+ if (diff1 > 0.0 || diff1 < 0.0) {
+ return diff1;
+ } else if (diff2 > 0.0 || diff2 < 0.0) {
+ return diff2;
+ } else {
+ return diff3;
+ }
+}
+
+struct sort_to_center {
+
+ std::vector<double> const& coords;
+ double cx;
+ double cy;
+
+ bool operator()(std::size_t i, std::size_t j) {
+ return compare(coords, i, j, cx, cy) < 0;
+ }
+};
+
+inline bool in_circle(
+ double ax,
+ double ay,
+ double bx,
+ double by,
+ double cx,
+ double cy,
+ double px,
+ double py) {
+ const double dx = ax - px;
+ const double dy = ay - py;
+ const double ex = bx - px;
+ const double ey = by - py;
+ const double fx = cx - px;
+ const double fy = cy - py;
+
+ const double ap = dx * dx + dy * dy;
+ const double bp = ex * ex + ey * ey;
+ const double cp = fx * fx + fy * fy;
+
+ return (dx * (ey * cp - bp * fy) -
+ dy * (ex * cp - bp * fx) +
+ ap * (ex * fy - ey * fx)) < 0.0;
+}
+
+inline bool check_pts_equal(double x1, double y1, double x2, double y2) {
+ return std::fabs(x1 - x2) < std::numeric_limits<double>::epsilon() &&
+ std::fabs(y1 - y2) < std::numeric_limits<double>::epsilon();
+}
+
+constexpr std::size_t INVALID_INDEX = std::numeric_limits<std::size_t>::max();
+
+struct DelaunatorPoint {
+ std::size_t i;
+ double x;
+ double y;
+ std::size_t t;
+ std::size_t prev;
+ std::size_t next;
+ bool removed;
+};
+
+class Delaunator {
+
+public:
+ std::vector<double> const& coords;
+ std::vector<std::size_t> triangles;
+ std::vector<std::size_t> halfedges;
+
+ Delaunator(std::vector<double> const& in_coords);
+
+private:
+ std::vector<std::size_t> m_hash;
+ std::vector<DelaunatorPoint> m_hull;
+ double m_center_x;
+ double m_center_y;
+ std::size_t m_hash_size;
+
+ std::size_t remove_node(std::size_t node);
+ std::size_t legalize(std::size_t a);
+ std::size_t insert_node(std::size_t i);
+ std::size_t insert_node(std::size_t i, std::size_t prev);
+ std::size_t hash_key(double x, double y);
+ void hash_edge(std::size_t e);
+ std::size_t add_triangle(
+ std::size_t i0,
+ std::size_t i1,
+ std::size_t i2,
+ std::size_t a,
+ std::size_t b,
+ std::size_t c);
+ void link(std::size_t a, std::size_t b);
+};
+
+Delaunator::Delaunator(std::vector<double> const& in_coords)
+ : coords(in_coords),
+ triangles(),
+ halfedges(),
+ m_hash(),
+ m_hull(),
+ m_center_x(),
+ m_center_y(),
+ m_hash_size() {
+ std::size_t n = coords.size() >> 1;
+
+ double max_x = std::numeric_limits<double>::min();
+ double max_y = std::numeric_limits<double>::min();
+ double min_x = std::numeric_limits<double>::max();
+ double min_y = std::numeric_limits<double>::max();
+ std::vector<std::size_t> ids;
+ ids.reserve(n);
+
+ for (std::size_t i = 0; i < n; i++) {
+ const double x = coords[2 * i];
+ const double y = coords[2 * i + 1];
+
+ if (x < min_x) min_x = x;
+ if (y < min_y) min_y = y;
+ if (x > max_x) max_x = x;
+ if (y > max_y) max_y = y;
+
+ ids.push_back(i);
+ }
+ const double cx = (min_x + max_x) / 2;
+ const double cy = (min_y + max_y) / 2;
+ double min_dist = std::numeric_limits<double>::max();
+
+ std::size_t i0 = INVALID_INDEX;
+ std::size_t i1 = INVALID_INDEX;
+ std::size_t i2 = INVALID_INDEX;
+
+ // pick a seed point close to the centroid
+ for (std::size_t i = 0; i < n; i++) {
+ const double d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]);
+ if (d < min_dist) {
+ i0 = i;
+ min_dist = d;
+ }
+ }
+
+ min_dist = std::numeric_limits<double>::max();
+
+ // find the point closest to the seed
+ for (std::size_t i = 0; i < n; i++) {
+ if (i == i0) continue;
+ const double d = dist(coords[2 * i0], coords[2 * i0 + 1], coords[2 * i], coords[2 * i + 1]);
+ if (d < min_dist && d > 0.0) {
+ i1 = i;
+ min_dist = d;
+ }
+ }
+
+ double min_radius = std::numeric_limits<double>::max();
+
+ // find the third point which forms the smallest circumcircle with the first two
+ for (std::size_t i = 0; i < n; i++) {
+ if (i == i0 || i == i1) continue;
+
+ const double r = circumradius(
+ coords[2 * i0], coords[2 * i0 + 1], coords[2 * i1], coords[2 * i1 + 1], coords[2 * i], coords[2 * i + 1]);
+
+ if (r < min_radius) {
+ i2 = i;
+ min_radius = r;
+ }
+ }
+
+ if (!(min_radius < std::numeric_limits<double>::max())) {
+ throw std::runtime_error("not triangulation");
+ ;
+ }
+
+ bool coord_area = area(
+ coords[2 * i0], coords[2 * i0 + 1], coords[2 * i1], coords[2 * i1 + 1], coords[2 * i2], coords[2 * i2 + 1]) < 0.0;
+ if (coord_area) {
+ std::swap(i1, i2);
+ }
+
+ const double i0x = coords[2 * i0];
+ const double i0y = coords[2 * i0 + 1];
+ const double i1x = coords[2 * i1];
+ const double i1y = coords[2 * i1 + 1];
+ const double i2x = coords[2 * i2];
+ const double i2y = coords[2 * i2 + 1];
+
+ std::tie(m_center_x, m_center_y) = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
+
+ // sort the points by distance from the seed triangle circumcenter
+ // cerr << ids << endl;
+ std::sort(ids.begin(), ids.end(), sort_to_center{ coords, m_center_x, m_center_y });
+ // quicksort(ids, coords, 0, n - 1, m_center_x, m_center_y);
+ // cerr << ids << endl;
+
+ m_hash_size = static_cast<std::size_t>(std::llround(std::ceil(std::sqrt(n))));
+ m_hash.reserve(m_hash_size);
+ for (std::size_t i = 0; i < m_hash_size; i++) {
+ m_hash.push_back(INVALID_INDEX);
+ }
+
+ m_hull.reserve(coords.size());
+
+ std::size_t e = insert_node(i0);
+ hash_edge(e);
+ m_hull[e].t = 0;
+
+ e = insert_node(i1, e);
+ hash_edge(e);
+ m_hull[e].t = 1;
+
+ e = insert_node(i2, e);
+ hash_edge(e);
+ m_hull[e].t = 2;
+
+ std::size_t max_triangles = n < 3 ? 1 : 2 * n - 5;
+ triangles.reserve(max_triangles * 3);
+ halfedges.reserve(max_triangles * 3);
+ add_triangle(i0, i1, i2, INVALID_INDEX, INVALID_INDEX, INVALID_INDEX);
+ double xp = std::numeric_limits<double>::quiet_NaN();
+ double yp = std::numeric_limits<double>::quiet_NaN();
+ for (std::size_t k = 0; k < n; k++) {
+ const std::size_t i = ids[k];
+ const double x = coords[2 * i];
+ const double y = coords[2 * i + 1];
+ if (check_pts_equal(x, y, xp, yp)) continue;
+ xp = x;
+ yp = y;
+ if (
+ check_pts_equal(x, y, i0x, i0y) ||
+ check_pts_equal(x, y, i1x, i1y) ||
+ check_pts_equal(x, y, i2x, i2y)) continue;
+
+ const std::size_t start_key = hash_key(x, y);
+ std::size_t key = start_key;
+ std::size_t start = INVALID_INDEX;
+ do {
+ start = m_hash[key];
+ key = (key + 1) % m_hash_size;
+ } while (
+ (start == INVALID_INDEX || m_hull[start].removed) &&
+ (key != start_key));
+
+ e = start;
+
+ while (
+ area(
+ x, y, m_hull[e].x, m_hull[e].y, m_hull[m_hull[e].next].x, m_hull[m_hull[e].next].y) >= 0.0) {
+ e = m_hull[e].next;
+
+ if (e == start) {
+ throw std::runtime_error("Something is wrong with the input points.");
+ }
+ }
+
+ const bool walk_back = e == start;
+
+ // add the first triangle from the point
+ std::size_t t = add_triangle(
+ m_hull[e].i,
+ i,
+ m_hull[m_hull[e].next].i,
+ INVALID_INDEX,
+ INVALID_INDEX,
+ m_hull[e].t);
+
+ m_hull[e].t = t; // keep track of boundary triangles on the hull
+ e = insert_node(i, e);
+
+ // recursively flip triangles from the point until they satisfy the Delaunay condition
+ m_hull[e].t = legalize(t + 2);
+ if (m_hull[m_hull[m_hull[e].prev].prev].t == halfedges[t + 1]) {
+ m_hull[m_hull[m_hull[e].prev].prev].t = t + 2;
+ }
+ // walk forward through the hull, adding more triangles and flipping recursively
+ std::size_t q = m_hull[e].next;
+ while (
+ area(
+ x, y, m_hull[q].x, m_hull[q].y, m_hull[m_hull[q].next].x, m_hull[m_hull[q].next].y) < 0.0) {
+ t = add_triangle(
+ m_hull[q].i, i, m_hull[m_hull[q].next].i, m_hull[m_hull[q].prev].t, INVALID_INDEX, m_hull[q].t);
+ m_hull[m_hull[q].prev].t = legalize(t + 2);
+ remove_node(q);
+ q = m_hull[q].next;
+ }
+ if (walk_back) {
+ // walk backward from the other side, adding more triangles and flipping
+ q = m_hull[e].prev;
+ while (
+ area(
+ x, y, m_hull[m_hull[q].prev].x, m_hull[m_hull[q].prev].y, m_hull[q].x, m_hull[q].y) < 0.0) {
+ t = add_triangle(
+ m_hull[m_hull[q].prev].i, i, m_hull[q].i, INVALID_INDEX, m_hull[q].t, m_hull[m_hull[q].prev].t);
+ legalize(t + 2);
+ m_hull[m_hull[q].prev].t = t;
+ remove_node(q);
+ q = m_hull[q].prev;
+ }
+ }
+ hash_edge(e);
+ hash_edge(m_hull[e].prev);
+ }
+}
+
+std::size_t Delaunator::remove_node(std::size_t node) {
+ m_hull[m_hull[node].prev].next = m_hull[node].next;
+ m_hull[m_hull[node].next].prev = m_hull[node].prev;
+ m_hull[node].removed = true;
+ return m_hull[node].prev;
+}
+
+std::size_t Delaunator::legalize(std::size_t a) {
+ std::size_t b = halfedges[a];
+
+ std::size_t a0 = a - a % 3;
+ std::size_t b0 = b - b % 3;
+
+ std::size_t al = a0 + (a + 1) % 3;
+ std::size_t ar = a0 + (a + 2) % 3;
+ std::size_t bl = b0 + (b + 2) % 3;
+
+ std::size_t p0 = triangles[ar];
+ std::size_t pr = triangles[a];
+ std::size_t pl = triangles[al];
+ std::size_t p1 = triangles[bl];
+
+ const bool illegal = in_circle(
+ coords[2 * p0], coords[2 * p0 + 1], coords[2 * pr], coords[2 * pr + 1], coords[2 * pl], coords[2 * pl + 1], coords[2 * p1], coords[2 * p1 + 1]);
+
+ if (illegal) {
+ triangles[a] = p1;
+ triangles[b] = p0;
+ link(a, halfedges[bl]);
+ link(b, halfedges[ar]);
+ link(ar, bl);
+
+ std::size_t br = b0 + (b + 1) % 3;
+
+ legalize(a);
+ return legalize(br);
+ }
+ return ar;
+}
+
+std::size_t Delaunator::insert_node(std::size_t i) {
+ std::size_t node = m_hull.size();
+ DelaunatorPoint p = {
+ i,
+ coords[2 * i],
+ coords[2 * i + 1],
+ 0,
+ node,
+ node,
+ false
+ };
+ m_hull.push_back(p);
+ return node;
+}
+
+std::size_t Delaunator::insert_node(std::size_t i, std::size_t prev) {
+ std::size_t node = insert_node(i);
+ m_hull[node].next = m_hull[prev].next;
+ m_hull[node].prev = prev;
+ m_hull[m_hull[node].next].prev = node;
+ m_hull[prev].next = node;
+ return node;
+}
+
+std::size_t Delaunator::hash_key(double x, double y) {
+ const double dx = x - m_center_x;
+ const double dy = y - m_center_y;
+ // use pseudo-angle: a measure that monotonically increases
+ // with real angle, but doesn't require expensive trigonometry
+ const double p = 1.0 - dx / (std::abs(dx) + std::abs(dy));
+ return static_cast<std::size_t>(std::llround(std::floor(
+ (2.0 + (dy < 0.0 ? -p : p)) / 4.0 * static_cast<double>(m_hash_size) //TODO:is this conversion save?
+ )));
+}
+
+void Delaunator::hash_edge(std::size_t e) {
+ m_hash[hash_key(m_hull[e].x, m_hull[e].y)] = e;
+}
+
+std::size_t Delaunator::add_triangle(
+ std::size_t i0,
+ std::size_t i1,
+ std::size_t i2,
+ std::size_t a,
+ std::size_t b,
+ std::size_t c) {
+ std::size_t t = triangles.size();
+ triangles.push_back(i0);
+ triangles.push_back(i1);
+ triangles.push_back(i2);
+ link(t, a);
+ link(t + 1, b);
+ link(t + 2, c);
+ return t;
+}
+
+void Delaunator::link(std::size_t a, std::size_t b) {
+ std::size_t s = halfedges.size();
+ if (a == s) {
+ halfedges.push_back(b);
+ } else if (a < s) {
+ halfedges[a] = b;
+ } else {
+ throw std::runtime_error("Cannot link edge");
+ }
+ if (b != INVALID_INDEX) {
+ std::size_t s2 = halfedges.size();
+ if (b == s2) {
+ halfedges.push_back(a);
+ } else if (b < s2) {
+ halfedges[b] = a;
+ } else {
+ throw std::runtime_error("Cannot link edge");
+ }
+ }
+}
+
+} //namespace delaunator
diff --git a/scripts/format.sh b/scripts/format.sh
new file mode 100755
index 0000000..e2e5165
--- /dev/null
+++ b/scripts/format.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+set -eu
+set -o pipefail
+
+: '
+
+Runs clang-format on the code in include/
+
+Return `1` if there are files to be formatted, and automatically formats them.
+
+Returns `0` if everything looks properly formatted.
+
+'
+ # Set up the environment by installing mason and clang++
+./scripts/setup.sh --config local.env
+source local.env
+
+# Add clang-format as a dep
+mason install clang-format ${MASON_LLVM_RELEASE}
+mason link clang-format ${MASON_LLVM_RELEASE}
+
+# Run clang-format on all cpp and hpp files in the /src directory
+find include/ bench/ test/ -type f -name '*.hpp' -or -name '*.cpp' \
+ | xargs -I{} clang-format -i -style=file {}
+
+# Print list of modified files
+dirty=$(git ls-files --modified include/ bench/ test/)
+
+if [[ $dirty ]]; then
+ echo "The following files have been modified:"
+ echo $dirty
+ exit 1
+else
+ exit 0
+fi
\ No newline at end of file
diff --git a/scripts/setup.sh b/scripts/setup.sh
new file mode 100755
index 0000000..42ce1ba
--- /dev/null
+++ b/scripts/setup.sh
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+
+set -eu
+set -o pipefail
+
+export MASON_RELEASE="${_MASON_RELEASE:-v0.18.0}"
+export MASON_LLVM_RELEASE="${_MASON_LLVM_RELEASE:-5.0.1}"
+
+PLATFORM=$(uname | tr A-Z a-z)
+if [[ ${PLATFORM} == 'darwin' ]]; then
+ PLATFORM="osx"
+fi
+
+MASON_URL="https://s3.amazonaws.com/mason-binaries/${PLATFORM}-$(uname -m)"
+
+llvm_toolchain_dir="$(pwd)/.toolchain"
+
+function run() {
+ local config=${1}
+ # unbreak bash shell due to rvm bug on osx: https://github.com/direnv/direnv/issues/210#issuecomment-203383459
+ # this impacts any usage of scripts that are source'd (like this one)
+ if [[ "${TRAVIS_OS_NAME:-}" == "osx" ]]; then
+ echo 'shell_session_update() { :; }' > ~/.direnvrc
+ fi
+
+ #
+ # COMPILER TOOLCHAIN
+ #
+
+ # We install clang++ without the mason client for a couple reasons:
+ # 1) decoupling makes it viable to use a custom branch of mason that might
+ # modify the upstream s3 bucket in a such a way that does not give
+ # it access to build tools like clang++
+ # 2) Allows us to short-circuit and use a global clang++ install if it
+ # is available to save space for local builds.
+ GLOBAL_CLANG="${HOME}/.mason/mason_packages/${PLATFORM}-$(uname -m)/clang++/${MASON_LLVM_RELEASE}"
+ GLOBAL_LLVM="${HOME}/.mason/mason_packages/${PLATFORM}-$(uname -m)/llvm/${MASON_LLVM_RELEASE}"
+ if [[ -d ${GLOBAL_LLVM} ]]; then
+ echo "Detected '${GLOBAL_LLVM}/bin/clang++', using it"
+ local llvm_toolchain_dir=${GLOBAL_LLVM}
+ elif [[ -d ${GLOBAL_CLANG} ]]; then
+ echo "Detected '${GLOBAL_CLANG}/bin/clang++', using it"
+ local llvm_toolchain_dir=${GLOBAL_CLANG}
+ elif [[ -d ${GLOBAL_CLANG} ]]; then
+ echo "Detected '${GLOBAL_CLANG}/bin/clang++', using it"
+ local llvm_toolchain_dir=${GLOBAL_CLANG}
+ elif [[ ! -d ${llvm_toolchain_dir} ]]; then
+ BINARY="${MASON_URL}/clang++/${MASON_LLVM_RELEASE}.tar.gz"
+ echo "Downloading ${BINARY}"
+ mkdir -p ${llvm_toolchain_dir}
+ curl -sSfL ${BINARY} | tar --gunzip --extract --strip-components=1 --directory=${llvm_toolchain_dir}
+ fi
+
+ #
+ # MASON
+ #
+
+ function setup_mason() {
+ local install_dir=${1}
+ local mason_release=${2}
+ mkdir -p ${install_dir}
+ curl -sSfL https://github.com/mapbox/mason/archive/${mason_release}.tar.gz | tar --gunzip --extract --strip-components=1 --directory=${install_dir}
+ }
+
+ setup_mason $(pwd)/.mason ${MASON_RELEASE}
+
+ #
+ # ENV SETTINGS
+ #
+
+ echo "export PATH=${llvm_toolchain_dir}/bin:$(pwd)/.mason:$(pwd)/mason_packages/.link/bin:"'${PATH}' > ${config}
+ echo "export CXX=${CXX:-${llvm_toolchain_dir}/bin/clang++}" >> ${config}
+ echo "export MASON_RELEASE=${MASON_RELEASE}" >> ${config}
+ echo "export MASON_LLVM_RELEASE=${MASON_LLVM_RELEASE}" >> ${config}
+ # https://github.com/google/sanitizers/wiki/AddressSanitizerAsDso
+ RT_BASE=${llvm_toolchain_dir}/lib/clang/${MASON_LLVM_RELEASE}/lib/$(uname | tr A-Z a-z)/libclang_rt
+ if [[ $(uname -s) == 'Darwin' ]]; then
+ RT_PRELOAD=${RT_BASE}.asan_osx_dynamic.dylib
+ else
+ RT_PRELOAD=${RT_BASE}.asan-x86_64.so
+ fi
+ echo "export MASON_LLVM_RT_PRELOAD=${RT_PRELOAD}" >> ${config}
+ SUPPRESSION_FILE="/tmp/leak_suppressions.txt"
+ # Add suppressions as needed
+ #echo "leak:__strdup" > ${SUPPRESSION_FILE}
+ echo "export ASAN_SYMBOLIZER_PATH=${llvm_toolchain_dir}/bin/llvm-symbolizer" >> ${config}
+ echo "export MSAN_SYMBOLIZER_PATH=${llvm_toolchain_dir}/bin/llvm-symbolizer" >> ${config}
+ echo "export UBSAN_OPTIONS=print_stacktrace=1" >> ${config}
+ if [[ -f ${SUPPRESSION_FILE} ]]; then
+ echo "export LSAN_OPTIONS=suppressions=${SUPPRESSION_FILE}" >> ${config}
+ fi
+ echo "export ASAN_OPTIONS=symbolize=1:abort_on_error=1:detect_container_overflow=1:check_initialization_order=1:detect_stack_use_after_return=1" >> ${config}
+ echo 'export MASON_SANITIZE="-fsanitize=address,undefined -fno-sanitize=vptr,function"' >> ${config}
+ echo 'export MASON_SANITIZE_CXXFLAGS="${MASON_SANITIZE} -fno-sanitize=vptr,function -fsanitize-address-use-after-scope -fno-omit-frame-pointer -fno-common"' >> ${config}
+ echo 'export MASON_SANITIZE_LDFLAGS="${MASON_SANITIZE}"' >> ${config}
+
+ exit 0
+}
+
+function usage() {
+ >&2 echo "Usage"
+ >&2 echo ""
+ >&2 echo "$ ./scripts/setup.sh --config local.env"
+ >&2 echo "$ source local.env"
+ >&2 echo ""
+ exit 1
+}
+
+if [[ ! ${1:-} ]]; then
+ usage
+fi
+
+# https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
+for i in "$@"
+do
+case $i in
+ --config)
+ if [[ ! ${2:-} ]]; then
+ usage
+ fi
+ shift
+ run $@
+ ;;
+ -h | --help)
+ usage
+ shift
+ ;;
+ *)
+ usage
+ ;;
+esac
+done
diff --git a/src/benchmark.cpp b/src/benchmark.cpp
deleted file mode 100644
index c4b80af..0000000
--- a/src/benchmark.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-
-#include <chrono>
-#include "delaunator.h"
-#include "json-helpers.h"
-#include <string>
-#include <vector>
-
-using namespace std;
-int main(int, char* argv[]) {
- string points_str = json_helpers::read_file("./test-files/osm-nodes-45331-epsg-3857.geojson");
- const vector<double> coords = json_helpers::get_geo_json_points(points_str);
-
- auto t_start = chrono::high_resolution_clock::now();
- Delaunator delaunator(move(coords));
- auto t_end = chrono::high_resolution_clock::now();
-
- auto milliseconds = chrono::duration_cast<chrono::milliseconds>(t_end - t_start).count();
-
- printf("coords=%lu \n", delaunator.coords.size() / 2);
- printf("milliseconds=%lld \n", milliseconds);
- printf("triangles=%lu \n", delaunator.triangles.size());
-
- return 0;
-}
\ No newline at end of file
diff --git a/src/delaunator-test.cpp b/src/delaunator-test.cpp
deleted file mode 100644
index 06d4ce0..0000000
--- a/src/delaunator-test.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-#define CATCH_CONFIG_MAIN
-#include "delaunator.h"
-#include "catch.hpp"
-#include "json-helpers.h"
-#include <string>
-#include <vector>
-
-using namespace std;
-
-namespace {
- void validate(const vector<double> &coords) {
- Delaunator d(coords);
-
- SECTION("validate halfedges") {
- for(size_t i = 0; i < d.halfedges.size(); i++) {
- const auto i2 = d.halfedges[i];
- REQUIRE(static_cast<bool>((i2 == -1) || (d.halfedges[i2] == i)));
- }
- }
-
- }
-}
-
-TEST_CASE("triangles match JS version ouput", "[Delaunator]") {
- string points_str = json_helpers::read_file("./test-files/playgrounds-1356-epsg-3857.geojson");
- string triangles_str = json_helpers::read_file("./test-files/playgrounds-1356-triangles.json");
- const vector<double> coords = json_helpers::get_geo_json_points(points_str);
- const vector<double> triangles = json_helpers::get_array_points(triangles_str);
- Delaunator delaunator(move(coords));
-
- SECTION("length of triangles is the same") {
- REQUIRE(delaunator.triangles.size() == triangles.size());
- }
-
- SECTION("values are the same") {
- for(size_t i = 0; i < triangles.size(); i++)
- {
- REQUIRE(delaunator.triangles[i] == triangles[i]);
- }
- }
-}
-
-TEST_CASE("produces correct triangulation", "[Delaunator]") {
- const vector<double> coords = {168, 180, 168, 178, 168, 179, 168, 181, 168, 183, 167, 183, 167, 184, 165, 184, 162, 186, 164, 188, 161, 188, 160, 191, 158, 193, 156, 193, 152, 195, 152, 198, 150, 198, 147, 198, 148, 205, 150, 210, 148, 210, 148, 208, 145, 206, 142, 206, 140, 206, 138, 206, 135, 206, 135, 209, 131, 209, 131, 211, 127, 211, 124, 210, 120, 207, 120, 204, 120, 202, 124, 201, 123, 201, 125, 198, 125, 194, 127, 194, 127, 191, 130, 191, 132, 189, 134, 189, 134, 186, 136, 184, 134, 182, 134, 179, 134, 176, 136, 174, 139, 174, 141, 177, 142, 176, 144, 176, 147, 178, 148, 176, 151, 178, 154, 178, 153, 175, 152, 174, 152, 170, 152, 168, 150, 166, 148, 166, 147, 165, 145, 162, 146, 160, 146, 157, 146, 155, 144, 155, 142, 152, 140, 150, 138, 150, 138, 148, 140, 145, 140, 142, 140, 138, 139, 138, 137, 138, 135, 138, 133, 135, 132, 132, 129, 132, 128, 132, 124, 132, 124, 130, 123, 130, 118, 126, 116, 124, 112, 122, 109, 122, 105, 122, 102, 124, 100, 124, 97, 124, 95, 126, 92, 127, 89, 127, 88, 130, 85, 132, 80, 134, 72, 134, 69, 134, 65, 138, 64, 138, 58, 137, 56, 133, 52, 133, 51, 133, 48, 133, 44, 133, 41, 131, 38, 130, 35, 130, 32, 127, 30, 127, 27, 127, 24, 127, 24, 126, 23, 124, 20, 122, 17, 122, 16, 118, 15, 116, 15, 110, 18, 108, 20, 102, 24, 97, 28, 102, 28, 98, 26, 97, 28, 94, 27, 85, 29, 79, 32, 76, 39, 70, 44, 66, 48, 65, 53, 61, 53, 58, 51, 54, 54, 54, 52, 48, 51, 43, 48, 42, 49, 38, 48, 34, 51, 30, 53, 33, 58, 30, 61, 30, 60, 27, 64, 26, 68, 24, 74, 24, 80, 24, 85, 26, 92, 26, 96, 29, 103, 32, 109, 33, 112, 37, 116, 37, 120, 37, 124, 35, 126, 35, 128, 38, 132, 38, 134, 41, 138, 38, 140, 36, 142, 40, 144, 43, 145, 41, 149, 41, 155, 41, 159, 41, 161, 46, 165, 46, 164, 42, 164, 39, 164, 34, 167, 30, 173, 24, 178, 24, 184, 24, 189, 26, 195, 21, 195, 20, 199, 20, 203, 20, 207, 17, 211, 17, 216, 17, 218, 16, 222, 22, 225, 27, 228, 31, 226, 34, 224, 34, 226, 39, 228, 43, 230, 46, 236, 46, 242, 46, 243, 50, 245, 50, 247, 54, 247, 56, 248, 60, 248, 65, 253, 66, 255, 64, 260, 64, 264, 67, 268, 71, 272, 66, 275, 66, 281, 61, 285, 66, 286, 70, 292, 74, 294, 74, 296, 74, 296, 71, 301, 74, 307, 74, 311, 78, 315, 74, 315, 77, 319, 77, 322, 82, 328, 82, 331, 81, 331, 84, 333, 86, 333, 90, 330, 95, 326, 98, 328, 99, 332, 98, 333, 101, 331, 104, 329, 104, 327, 106, 329, 111, 332, 116, 333, 119, 333, 122, 332, 126, 332, 130, 327, 130, 321, 130, 317, 130, 315, 134, 312, 134, 308, 138, 306, 138, 306, 144, 306, 149, 306, 152, 301, 152, 297, 154, 295, 154, 292, 154, 292, 158, 288, 158, 283, 162, 281, 164, 279, 163, 276, 163, 273, 166, 272, 169, 268, 168, 265, 170, 260, 172, 256, 176, 252, 176, 248, 181, 246, 182, 246, 189, 246, 194, 248, 197, 250, 198, 252, 200, 252, 203, 254, 205, 260, 205, 264, 202, 267, 202, 269, 202, 272, 199, 280, 199, 278, 202, 278, 207, 278, 211, 276, 211, 272, 213, 268, 213, 265, 213, 264, 211, 262, 210, 260, 210, 257, 212, 257, 214, 255, 217, 253, 217, 253, 221, 249, 220, 247, 220, 243, 222, 240, 223, 239, 226, 234, 231, 229, 231, 224, 231, 219, 227, 220, 227, 222, 224, 222, 222, 222, 219, 224, 217, 222, 214, 220, 212, 217, 210, 215, 210, 211, 209, 208, 206, 202, 209, 202, 205, 206, 202, 211, 198, 216, 195, 220, 192, 224, 192, 221, 186, 218, 186, 214, 185, 208, 185, 204, 186, 200, 186, 193, 183, 190, 182, 188, 182, 190, 178, 186, 178, 184, 174, 182, 171, 178, 171, 173, 174, 169, 174, 169, 175, 169, 179, 167, 182, 164, 186, 160, 192, 155, 195, 152, 198, 150, 198, 148, 198, 148, 202, 151, 208, 148, 210, 146, 208, 144, 205, 140, 205, 137, 208, 132, 208, 132, 210, 127, 210, 124, 210, 120, 206, 120, 202, 123, 202, 124, 201, 124, 198, 128, 195, 131, 191, 133, 187, 135, 183, 130, 203, 129, 208, 123, 203, 129, 203, 129, 198, 133, 198, 136, 200, 142, 200, 143, 199, 143, 197, 137, 196, 136, 194, 133, 194, 136, 186, 136, 182, 141, 186, 144, 186, 150, 186, 150, 190, 155, 190, 159, 188, 156, 182, 151, 182, 144, 182, 164, 176, 161, 177, 157, 177, 166, 176, 168, 165, 175, 167, 180, 167, 188, 159, 195, 164, 195, 162, 187, 162, 178, 163, 173, 166, 168, 170, 156, 170, 157, 165, 164, 165, 164, 161, 170, 159, 167, 158, 159, 154, 149, 151, 145, 145, 145, 138, 152, 138, 152, 146, 159, 146, 165, 153, 176, 153, 180, 153, 187, 153, 194, 153, 202, 153, 202, 158, 197, 158, 193, 158, 193, 142, 180, 142, 171, 142, 163, 135, 176, 135, 186, 139, 201, 139, 206, 139, 205, 147, 205, 160, 198, 160, 206, 174, 205, 178, 196, 178, 196, 182, 202, 182, 206, 181, 209, 181, 215, 181, 222, 181, 230, 177, 238, 175, 241, 175, 237, 175, 237, 168, 237, 161, 232, 156, 231, 162, 225, 166, 217, 169, 210, 173, 224, 173, 227, 173, 235, 175, 237, 178, 228, 192, 222, 199, 216, 199, 211, 204, 205, 206, 219, 207, 222, 211, 229, 214, 236, 214, 244, 211, 247, 211, 268, 206, 277, 201, 279, 201, 281, 202, 278, 202, 242, 178, 236, 170, 236, 162, 255, 162, 251, 156, 240, 156, 253, 152, 261, 152, 277, 157, 268, 151, 255, 143, 260, 142, 267, 145, 271, 149, 273, 154, 258, 146, 257, 131, 256, 134, 248, 137, 260, 137, 260, 134, 271, 137, 276, 138, 276, 144, 289, 144, 285, 150, 294, 150, 298, 149, 301, 145, 292, 145, 282, 134, 276, 134, 283, 127, 282, 116, 277, 113, 283, 113, 288, 106, 296, 106, 297, 113, 297, 118, 298, 118, 310, 122, 310, 128, 300, 130, 300, 140, 292, 129, 292, 114, 283, 122, 289, 122, 299, 122, 299, 134, 294, 134, 288, 124, 314, 121, 311, 113, 308, 110, 304, 96, 299, 90, 299, 82, 305, 87, 309, 94, 311, 101, 312, 102, 314, 107, 320, 112, 320, 115, 326, 116, 323, 109, 321, 102, 321, 94, 321, 90, 328, 90, 328, 88, 316, 88, 316, 84, 307, 84, 290, 77, 289, 88, 289, 97, 278, 97, 268, 106, 268, 110, 261, 105, 255, 103, 244, 103, 252, 100, 252, 91, 252, 82, 242, 78, 252, 78, 259, 78, 264, 87, 267, 92, 272, 91, 272, 83, 264, 83, 260, 79, 276, 79, 283, 84, 283, 94, 289, 94, 284, 86, 272, 77, 253, 110, 248, 110, 239, 110, 234, 114, 222, 125, 219, 127, 219, 131, 219, 138, 219, 141, 224, 139, 224, 135, 225, 130, 232, 136, 240, 138, 237, 131, 237, 118, 248, 120, 256, 122, 262, 127, 255, 118, 245, 110, 207, 129, 199, 134, 195, 134, 188, 130, 180, 130, 165, 129, 156, 129, 165, 128, 173, 125, 185, 126, 193, 126, 201, 124, 204, 123, 208, 116, 214, 114, 207, 114, 196, 114, 183, 121, 183, 111, 189, 117, 196, 112, 172, 126, 164, 126, 159, 114, 174, 106, 186, 106, 192, 105, 184, 105, 184, 96, 173, 96, 163, 111, 159, 110, 152, 110, 168, 110, 171, 106, 183, 98, 193, 101, 219, 96, 225, 97, 225, 104, 232, 92, 240, 92, 237, 86, 229, 86, 216, 88, 214, 79, 203, 79, 203, 75, 212, 75, 221, 75, 229, 80, 230, 89, 217, 88, 217, 77, 228, 77, 228, 69, 235, 71, 240, 71, 244, 66, 236, 54, 236, 62, 232, 68, 229, 61, 216, 61, 212, 58, 212, 47, 212, 39, 214, 28, 215, 48, 225, 55, 236, 55, 202, 65, 202, 54, 202, 44, 202, 24, 198, 32, 199, 38, 192, 38, 185, 38, 174, 42, 174, 48, 178, 51, 184, 51, 194, 55, 191, 68, 182, 68, 174, 69, 167, 67, 153, 59, 153, 49, 147, 49, 152, 58, 152, 74, 154, 83, 161, 83, 165, 88, 153, 97, 153, 89, 152, 82, 168, 88, 168, 101, 156, 102, 156, 119, 173, 110, 184, 110, 177, 106, 160, 106, 145, 125, 137, 122, 131, 120, 124, 120, 122, 118, 113, 118, 114, 111, 129, 111, 140, 110, 143, 106, 137, 102, 127, 102, 119, 98, 126, 93, 139, 93, 139, 99, 141, 95, 128, 89, 118, 74, 128, 76, 135, 76, 141, 83, 141, 71, 137, 61, 137, 50, 129, 50, 118, 50, 109, 52, 112, 61, 123, 60, 134, 60, 129, 76, 121, 67, 124, 76, 123, 76, 111, 74, 128, 73, 109, 83, 109, 94, 105, 103, 102, 118, 92, 113, 98, 105, 99, 93, 94, 93, 94, 81, 99, 81, 100, 73, 100, 89, 100, 60, 100, 55, 105, 37, 101, 34, 93, 37, 90, 37, 90, 49, 99, 49, 88, 68, 80, 68, 78, 64, 88, 62, 86, 77, 76, 89, 71, 91, 71, 106, 78, 106, 82, 118, 84, 110, 71, 104, 76, 103, 76, 91, 78, 83, 85, 89, 83, 103, 83, 119, 76, 130, 62, 130, 68, 127, 74, 126, 83, 123, 62, 123, 56, 123, 59, 129, 59, 120, 49, 110, 46, 106, 56, 100, 62, 94, 62, 109, 72, 112, 67, 112, 57, 112, 61, 122, 60, 102, 52, 125, 44, 121, 36, 114, 32, 110, 20, 110, 22, 118, 35, 118, 44, 124, 32, 119, 22, 111, 44, 96, 36, 106, 36, 94, 32, 94, 35, 83, 44, 91, 52, 91, 52, 80, 59, 80, 62, 76, 62, 70, 47, 78, 55, 75, 64, 71, 64, 60, 58, 53, 58, 43, 65, 43, 65, 60, 76, 52, 73, 38, 76, 36, 93, 48, 89, 39, 99, 40, 98, 50, 94, 63, 117, 63, 131, 67, 131, 74, 142, 78, 140, 61, 124, 58, 124, 48, 136, 55, 236, 200, 228, 200, 226, 192, 232, 198, 238, 210, 248, 210, 236, 220, 230, 223, 230, 213, 175, 32, 172, 32, 171, 38, 184, 30};
- validate(coords);
-}
diff --git a/src/delaunator.cpp b/src/delaunator.cpp
deleted file mode 100644
index 73b9d6f..0000000
--- a/src/delaunator.cpp
+++ /dev/null
@@ -1,485 +0,0 @@
-
-#include "delaunator.h"
-#include <algorithm>
-#include <cstdio>
-#include <limits>
-#include <tuple>
-#include <stdexcept>
-#include <cmath>
-// #include "prettyprint.hpp"
-#include <iostream>
-
-using namespace std;
-
-namespace {
- const double max_double = numeric_limits<double>::max();
- double dist(
- const double &ax,
- const double &ay,
- const double &bx,
- const double &by
- ) {
- const double dx = ax - bx;
- const double dy = ay - by;
- return dx * dx + dy * dy;
- }
- double circumradius(
- const double &ax,
- const double &ay,
- const double &bx,
- const double &by,
- const double &cx,
- const double &cy
- ) {
- const double dx = bx - ax;
- const double dy = by - ay;
- const double ex = cx - ax;
- const double ey = cy - ay;
-
- const double bl = dx * dx + dy * dy;
- const double cl = ex * ex + ey * ey;
- const double d = dx * ey - dy * ex;
-
- const double x = (ey * bl - dy * cl) * 0.5 / d;
- const double y = (dx * cl - ex * bl) * 0.5 / d;
-
- if (bl && cl && d) {
- return x * x + y * y;
- } else {
- return max_double;
- }
- }
-
- double area(
- const double px,
- const double py,
- const double qx,
- const double qy,
- const double rx,
- const double ry
- ) {
- return (qy - py) * (rx - qx) - (qx - px) * (ry - qy);
- }
-
- tuple<double, double> circumcenter(
- const double &ax,
- const double &ay,
- const double &bx,
- const double &by,
- const double &cx,
- const double &cy
- ) {
- const double dx = bx - ax;
- const double dy = by - ay;
- const double ex = cx - ax;
- const double ey = cy - ay;
-
- const double bl = dx * dx + dy * dy;
- const double cl = ex * ex + ey * ey;
- const double d = dx * ey - dy * ex;
-
- const double x = ax + (ey * bl - dy * cl) * 0.5 / d;
- const double y = ay + (dx * cl - ex * bl) * 0.5 / d;
-
- return make_tuple(x, y);
- }
- double compare(
- const vector<double> &coords,
- unsigned long int i,
- unsigned long int j,
- double cx,
- double cy
- ) {
- const double d1 = dist(coords[2 * i], coords[2 * i + 1], cx, cy);
- const double d2 = dist(coords[2 * j], coords[2 * j + 1], cx, cy);
- const double diff1 = d1 - d2;
- const double diff2 = coords[2 * i] - coords[2 * j];
- const double diff3 = coords[2 * i + 1] - coords[2 * j + 1];
-
- if (diff1) {
- return diff1;
- } else if(diff2) {
- return diff2;
- } else {
- return diff3;
- }
- }
- struct sort_to_center {
- double cx;
- double cy;
- vector<double> &coords;
- bool operator() (long int i, long int j) {
- return compare(coords, i, j, cx, cy) < 0;
- }
- };
-
- bool in_circle(
- double ax, double ay,
- double bx, double by,
- double cx, double cy,
- double px, double py
- ) {
- const double dx = ax - px;
- const double dy = ay - py;
- const double ex = bx - px;
- const double ey = by - py;
- const double fx = cx - px;
- const double fy = cy - py;
-
- const double ap = dx * dx + dy * dy;
- const double bp = ex * ex + ey * ey;
- const double cp = fx * fx + fy * fy;
-
- return (dx * (ey * cp - bp * fy) -
- dy * (ex * cp - bp * fx) +
- ap * (ex * fy - ey * fx)) < 0;
- }
-}
-
-Delaunator::Delaunator(vector<double> in_coords) {
- coords = move(in_coords);
- const long int n = coords.size() >> 1;
- double max_x = -1 * max_double;
- double max_y = -1 * max_double;
- double min_x = max_double;
- double min_y = max_double;
- vector<long int> ids;
- ids.reserve(n);
- for (long int i = 0; i < n; i++) {
- const double x = coords[2 * i];
- const double y = coords[2 * i + 1];
- // printf("%f %f", x, y);
-
- if (x < min_x) min_x = x;
- if (y < min_y) min_y = y;
- if (x > max_x) max_x = x;
- if (y > max_y) max_y = y;
- // ids[i] = i;
- ids.push_back(i);
- }
- const double cx = (min_x + max_x) / 2;
- const double cy = (min_y + max_y) / 2;
- double min_dist = max_double;
- unsigned long int i0;
- unsigned long int i1;
- unsigned long int i2;
-
- // pick a seed point close to the centroid
- for (unsigned long int i = 0; i < n; i++) {
- const double d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]);
- if (d < min_dist) {
- i0 = i;
- min_dist = d;
- }
- }
-
- min_dist = max_double;
-
- // find the point closest to the seed
- for (unsigned long int i = 0; i < n; i++) {
- if (i == i0) continue;
- const double d = dist(coords[2 * i0], coords[2 * i0 + 1], coords[2 * i], coords[2 * i + 1]);
- if (d < min_dist && d > 0)
- {
- i1 = i;
- min_dist = d;
- }
- }
-
- double min_radius = max_double;
-
- // find the third point which forms the smallest circumcircle with the first two
- for (unsigned long int i = 0; i < n; i++)
- {
- if (i == i0 || i == i1) continue;
-
- const double r = circumradius(
- coords[2 * i0], coords[2 * i0 + 1],
- coords[2 * i1], coords[2 * i1 + 1],
- coords[2 * i], coords[2 * i + 1]);
-
- if (r < min_radius)
- {
- i2 = i;
- min_radius = r;
- }
- }
-
- if (min_radius == max_double) {
- throw runtime_error("not triangulation");;
- }
-
- if (
- area(
- coords[2 * i0], coords[2 * i0 + 1],
- coords[2 * i1], coords[2 * i1 + 1],
- coords[2 * i2], coords[2 * i2 + 1]
- ) < 0
- ) {
- const double tmp = i1;
- i1 = i2;
- i2 = tmp;
- }
-
- const double i0x = coords[2 * i0];
- const double i0y = coords[2 * i0 + 1];
- const double i1x = coords[2 * i1];
- const double i1y = coords[2 * i1 + 1];
- const double i2x = coords[2 * i2];
- const double i2y = coords[2 * i2 + 1];
-
- tie(m_center_x, m_center_y) = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
-
- // sort the points by distance from the seed triangle circumcenter
- // cerr << ids << endl;
- const sort_to_center s = {
- .cx = m_center_x,
- .cy = m_center_y,
- .coords = coords
- };
- sort(ids.begin(), ids.end(), s);
- // quicksort(ids, coords, 0, n - 1, m_center_x, m_center_y);
- // cerr << ids << endl;
-
- m_hash_size = ceil(sqrt(n));
- m_hash.reserve(m_hash_size);
- for (int i = 0; i < m_hash_size; i++) m_hash.push_back(-1);
-
- m_hull.reserve(coords.size());
-
- long int e = insert_node(i0);
- hash_edge(e);
- m_hull[e].t = 0;
-
- e = insert_node(i1, e);
- hash_edge(e);
- m_hull[e].t = 1;
-
- e = insert_node(i2, e);
- hash_edge(e);
- m_hull[e].t = 2;
-
- const long int max_triangles = 2 * n - 5;
- triangles.reserve(max_triangles * 3);
- halfedges.reserve(max_triangles * 3);
- add_triangle(i0, i1, i2, -1, -1, -1);
- double xp = NAN;
- double yp = NAN;
- for (long int k = 0; k < n; k++) {
- const long int i = ids[k];
- const double x = coords[2 * i];
- const double y = coords[2 * i + 1];
- if (x == xp && y == yp) continue;
- xp = x;
- yp = y;
- if (
- (x == i0x && y == i0y) ||
- (x == i1x && y == i1y) ||
- (x == i2x && y == i2y)
- ) continue;
-
- const long int start_key = hash_key(x, y);
- long int key = start_key;
- long int start = -1;
- do {
- start = m_hash[key];
- key = (key + 1) % m_hash_size;
- } while(
- (start < 0 || m_hull[start].removed) &&
- (key != start_key)
- );
-
- e = start;
-
- while(
- area(
- x, y,
- m_hull[e].x, m_hull[e].y,
- m_hull[m_hull[e].next].x, m_hull[m_hull[e].next].y
- ) >= 0
- ) {
- e = m_hull[e].next;
-
- if (e == start) {
- throw runtime_error("Something is wrong with the input points.");
- }
- }
-
- const bool walk_back = e == start;
-
- // add the first triangle from the point
- long int t = add_triangle(
- m_hull[e].i,
- i,
- m_hull[m_hull[e].next].i,
- -1, -1, m_hull[e].t
- );
-
- m_hull[e].t = t; // keep track of boundary triangles on the hull
- e = insert_node(i, e);
-
- // recursively flip triangles from the point until they satisfy the Delaunay condition
- m_hull[e].t = legalize(t + 2);
- if (m_hull[m_hull[m_hull[e].prev].prev].t == halfedges[t + 1]) {
- m_hull[m_hull[m_hull[e].prev].prev].t = t + 2;
- }
- // walk forward through the hull, adding more triangles and flipping recursively
- long int q = m_hull[e].next;
- while(
- area(
- x, y,
- m_hull[q].x, m_hull[q].y,
- m_hull[m_hull[q].next].x, m_hull[m_hull[q].next].y
- ) < 0
- ) {
- t = add_triangle(
- m_hull[q].i, i,
- m_hull[m_hull[q].next].i, m_hull[m_hull[q].prev].t,
- -1, m_hull[q].t
- );
- m_hull[m_hull[q].prev].t = legalize(t + 2);
- remove_node(q);
- q = m_hull[q].next;
- }
- if (walk_back) {
- // walk backward from the other side, adding more triangles and flipping
- q = m_hull[e].prev;
- while(
- area(
- x, y,
- m_hull[m_hull[q].prev].x, m_hull[m_hull[q].prev].y,
- m_hull[q].x, m_hull[q].y
- ) < 0
- ) {
- t = add_triangle(
- m_hull[m_hull[q].prev].i, i,
- m_hull[q].i, -1,
- m_hull[q].t, m_hull[m_hull[q].prev].t
- );
- legalize(t + 2);
- m_hull[m_hull[q].prev].t = t;
- remove_node(q);
- q = m_hull[q].prev;
- }
- }
- hash_edge(e);
- hash_edge(m_hull[e].prev);
- }
-
-};
-
-long int Delaunator::remove_node(long int node) {
- m_hull[m_hull[node].prev].next = m_hull[node].next;
- m_hull[m_hull[node].next].prev = m_hull[node].prev;
- m_hull[node].removed = true;
- return m_hull[node].prev;
-}
-
-long int Delaunator::legalize(long int a) {
- long int halfedges_size = halfedges.size();
- const long int b = halfedges[a];
-
- const long int a0 = a - a % 3;
- const long int b0 = b - b % 3;
-
- const long int al = a0 + (a + 1) % 3;
- const long int ar = a0 + (a + 2) % 3;
- const long int bl = b0 + (b + 2) % 3;
-
- const long int p0 = triangles[ar];
- const long int pr = triangles[a];
- const long int pl = triangles[al];
- const long int p1 = triangles[bl];
-
- const bool illegal = in_circle(
- coords[2 * p0], coords[2 * p0 + 1],
- coords[2 * pr], coords[2 * pr + 1],
- coords[2 * pl], coords[2 * pl + 1],
- coords[2 * p1], coords[2 * p1 + 1]
- );
-
- if (illegal) {
- triangles[a] = p1;
- triangles[b] = p0;
- link(a, halfedges[bl]);
- link(b, halfedges[ar]);
- link(ar, bl);
-
- const long int br = b0 + (b + 1) % 3;
-
- legalize(a);
- return legalize(br);
- }
- return ar;
-}
-
-long int Delaunator::insert_node(long int i, long int prev) {
- const long int node = insert_node(i);
- m_hull[node].next = m_hull[prev].next;
- m_hull[node].prev = prev;
- m_hull[m_hull[node].next].prev = node;
- m_hull[prev].next = node;
- return node;
-};
-
-long int Delaunator::insert_node(long int i) {
- long int node = m_hull.size();
- DelaunatorPoint p = {
- .i = i,
- .x = coords[2 * i],
- .y = coords[2 * i + 1],
- .prev = node,
- .next = node,
- .removed = false
- };
- m_hull.push_back(move(p));
- return node;
-}
-
-long int Delaunator::hash_key(double x, double y) {
- const double dx = x - m_center_x;
- const double dy = y - m_center_y;
- // use pseudo-angle: a measure that monotonically increases
- // with real angle, but doesn't require expensive trigonometry
- const double p = 1 - dx / (abs(dx) + abs(dy));
- return floor((2 + (dy < 0 ? -p : p)) / 4 * m_hash_size);
-}
-
-void Delaunator::hash_edge(long int e){
- m_hash[hash_key(m_hull[e].x, m_hull[e].y)] = e;
-}
-
-long int Delaunator::add_triangle(
- long int i0, long int i1, long int i2,
- long int a, long int b, long int c
-) {
- const long int t = triangles.size();
- triangles.push_back(i0);
- triangles.push_back(i1);
- triangles.push_back(i2);
- link(t, a);
- link(t + 1, b);
- link(t + 2, c);
- return t;
-}
-
-void Delaunator::link(long int a, long int b) {
- long int s = halfedges.size();
- if (a == s) {
- halfedges.push_back(b);
- } else if (a < s) {
- halfedges[a] = b;
- } else {
- throw runtime_error("Cannot link edge");
- }
- if (b != -1) {
- long int s = halfedges.size();
- if (b == s) {
- halfedges.push_back(a);
- } else if (b < s) {
- halfedges[b] = a;
- } else {
- throw runtime_error("Cannot link edge");
- }
- }
-};
\ No newline at end of file
diff --git a/src/delaunator.h b/src/delaunator.h
deleted file mode 100644
index a95b871..0000000
--- a/src/delaunator.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-#include <vector>
-#include <exception>
-#include <deque>
-#include <memory>
-
-struct DelaunatorPoint {
- long int i;
- double x;
- double y;
- long int t;
- long int prev;
- long int next;
- bool removed;
-};
-
-class Delaunator{
- public:
- Delaunator(std::vector<double> in_coords);
- std::vector<unsigned long int> triangles;
- std::vector<long int> halfedges;
- std::vector<double> coords;
- private:
- double m_center_x;
- double m_center_y;
- long int m_hash_size;
- std::vector<int> m_hash;
- std::vector<DelaunatorPoint> m_hull;
- long int insert_node(long int i);
- long int insert_node(long int i, long int prev);
- long int hash_key(double x, double y);
- void hash_edge(long int e);
- long int add_triangle(
- long int i0, long int i1, long int i2,
- long int a, long int b, long int c
- );
- void link(long int a, long int b);
- long int legalize(long int a);
- long int remove_node(long int node);
-};
diff --git a/src/json-helpers.cpp b/src/json-helpers.cpp
deleted file mode 100644
index 1d65259..0000000
--- a/src/json-helpers.cpp
+++ /dev/null
@@ -1,56 +0,0 @@
-
-#include "json-helpers.h"
-#include <fstream>
-#include <stdexcept>
-#include "rapidjson/document.h"
-
-using namespace std;
-
-string json_helpers::read_file(const char* filename) {
- ifstream input_file(filename);
- if(input_file.good()) {
- string json_str(
- (istreambuf_iterator<char>(input_file)),
- istreambuf_iterator<char>()
- );
- return json_str;
- } else {
- printf("Error reading file %s", filename);
- throw runtime_error("Error reading file");
- }
-}
-
-vector<double> json_helpers::get_geo_json_points(const string& json) {
- rapidjson::Document document;
- if(document.Parse(json.c_str()).HasParseError()) {
- throw runtime_error("Cannot parse JSON");
- }
- const rapidjson::Value& features = document["features"];
- vector<double> coords;
- // vector<double> y_vector;
- for(rapidjson::SizeType i = 0; i < features.Size(); i++) {
- const rapidjson::Value& coordinates = features[i]["geometry"]["coordinates"];
- const double x = coordinates[0].GetDouble();
- const double y = coordinates[1].GetDouble();
- coords.push_back(x);
- coords.push_back(y);
- }
- return coords;
-}
-
-vector<double> json_helpers::get_array_points(const string& json) {
- vector<double> points;
- rapidjson::Document document;
- if(document.Parse(json.c_str()).HasParseError()) {
- throw runtime_error("Cannot parse JSON");
- }
- if(!document.IsArray()) {
- throw runtime_error("It's not JSON Array");
- }
- points.reserve(static_cast<int>(document.Size()));
- for(rapidjson::SizeType i = 0; i < document.Size(); i++) {
- points.push_back(document[i].GetDouble());
- }
- return points;
-
-}
diff --git a/src/json-helpers.h b/src/json-helpers.h
deleted file mode 100644
index e48f3c9..0000000
--- a/src/json-helpers.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#include <string>
-#include <vector>
-
-namespace json_helpers {
- std::string read_file(const char* filename);
- std::vector<double> get_geo_json_points(const std::string& json);
- std::vector<double> get_array_points(const std::string& json);
-}
diff --git a/src/triangulate.cpp b/src/triangulate.cpp
deleted file mode 100644
index abe1fde..0000000
--- a/src/triangulate.cpp
+++ /dev/null
@@ -1,86 +0,0 @@
-#include "rapidjson/document.h"
-#include "rapidjson/prettywriter.h"
-#include "delaunator.h"
-#include "json-helpers.h"
-#include <cstdio>
-#include <fstream>
-#include <vector>
-#include <initializer_list>
-// #include "prettyprint.hpp"
-#include <iostream>
-using namespace std;
-
-namespace {
-
- const string serialize_to_json(Delaunator &delaunator) {
- rapidjson::StringBuffer sb;
- rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(sb);
- writer.StartObject();
- writer.String("type"); writer.String("FeatureCollection");
- writer.String("crs");
- writer.StartObject();
- writer.String("type"); writer.String("name");
- writer.String("properties");
- writer.StartObject();
- writer.String("name"); writer.String("urn:ogc:def:crs:EPSG::900913");
- writer.EndObject();
- writer.EndObject();
- writer.String("features");
- writer.StartArray();
- for(long int i = 0; i < delaunator.triangles.size(); i+=3) {
- writer.StartObject();
- writer.String("type"); writer.String("Feature");
- writer.String("properties"); writer.StartObject(); writer.EndObject();
- writer.String("geometry");
- writer.StartObject();
- writer.String("type"); writer.String("Polygon");
- writer.String("coordinates");
- writer.StartArray();
- writer.StartArray();
- writer.StartArray();
- writer.Uint(delaunator.coords[2 * delaunator.triangles[i]]);
- writer.Uint(delaunator.coords[2 * delaunator.triangles[i] + 1]);
- writer.EndArray();
- writer.StartArray();
- writer.Uint(delaunator.coords[2 * delaunator.triangles[i + 1]]);
- writer.Uint(delaunator.coords[2 * delaunator.triangles[i + 1] + 1]);
- writer.EndArray();
- writer.StartArray();
- writer.Uint(delaunator.coords[2 * delaunator.triangles[i + 2]]);
- writer.Uint(delaunator.coords[2 * delaunator.triangles[i + 2] + 1]);
- writer.EndArray();
- writer.StartArray();
- writer.Uint(delaunator.coords[2 * delaunator.triangles[i]]);
- writer.Uint(delaunator.coords[2 * delaunator.triangles[i] + 1]);
- writer.EndArray();
- writer.EndArray();
- writer.EndArray();
- writer.EndObject();
- writer.EndObject();
- }
- writer.EndArray();
- writer.EndObject();
- return string(sb.GetString());
- }
-}
-
-
-int main(int, char* argv[]) {
- const char* filename = argv[1];
- const char* output = argv[2];
- string json = json_helpers::read_file(filename);
- const vector<double> coords = json_helpers::get_geo_json_points(json);
- Delaunator delaunator(move(coords));
- const char* out_json = serialize_to_json(delaunator).c_str();
-
- if (output) {
- printf("Writing to file %s", output);
- ofstream stream;
- stream.open(output);
- stream << out_json;
- stream.close();
- } else {
- puts(out_json);
- }
- return 0;
-}
diff --git a/test/delaunator.test.cpp b/test/delaunator.test.cpp
new file mode 100644
index 0000000..4138bbd
--- /dev/null
+++ b/test/delaunator.test.cpp
@@ -0,0 +1,42 @@
+#include "../examples/utils.hpp"
+#include <catch.hpp>
+#include <delaunator.hpp>
+
+namespace {
+void validate(const std::vector<double>& coords) {
+ delaunator::Delaunator d(coords);
+
+ SECTION("validate halfedges") {
+ for (std::size_t i = 0; i < d.halfedges.size(); i++) {
+ const auto i2 = d.halfedges[i];
+ REQUIRE(static_cast<bool>(
+ (i2 == delaunator::INVALID_INDEX) || (d.halfedges[i2] == i)));
+ }
+ }
+}
+} // namespace
+
+TEST_CASE("triangles match JS version ouput", "[Delaunator]") {
+ std::string points_str = utils::read_file("./test/test-files/playgrounds-1356-epsg-3857.geojson");
+ std::string triangles_str = utils::read_file("./test/test-files/playgrounds-1356-triangles.json");
+ std::vector<double> coords = utils::get_geo_json_points(points_str);
+ std::vector<double> triangles = utils::get_array_points(triangles_str);
+ delaunator::Delaunator delaunator(coords);
+
+ SECTION("length of triangles is the same") {
+ REQUIRE(delaunator.triangles.size() == triangles.size());
+ }
+
+ SECTION("values are the same") {
+ for (std::size_t i = 0; i < triangles.size(); i++) {
+ REQUIRE(delaunator.triangles[i] == Approx(triangles[i]));
+ }
+ }
+}
+
+TEST_CASE("produces correct triangulation", "[Delaunator]") {
+ // clang-format off
+ std::vector<double> coords = {168, 180, 168, 178, 168, 179, 168, 181, 168, 183, 167, 183, 167, 184, 165, 184, 162, 186, 164, 188, 161, 188, 160, 191, 158, 193, 156, 193, 152, 195, 152, 198, 150, 198, 147, 198, 148, 205, 150, 210, 148, 210, 148, 208, 145, 206, 142, 206, 140, 206, 138, 206, 135, 206, 135, 209, 131, 209, 131, 211, 127, 211, 124, 210, 120, 207, 120, 204, 120, 202, 124, 201, 123, 201, 125, 198, 125, 194, 127, 194, 127, 191, 130, 191, 132, 189, 134, 189, 134, 186, 136, 184, 134, 182, 134, 179, 134, 176, 136, 174, 139, 174, 141, 177, 142, 176, 144, 176, 147, 178, 148, 176, 151, 178, 154, 178, 153, 175, 152, 174, 152, 170, 152, 168, 150, 166, 148, 166, 147, 165, 145, 162, 146, 160, 146, 157, 146, 155, 144, 155, 142, 152, 140, 150, 138, 150, 138, 148, 140, 145, 140, 142, 140, 138, 139, 138, 137, 138, 135, 138, 133, 135, 132, 132, 129, 132, 128, 132, 124, 132, 124, 130, 123, 130, 118, 126, 116, 124, 112, 122, 109, 122, 105, 122, 102, 124, 100, 124, 97, 124, 95, 126, 92, 127, 89, 127, 88, 130, 85, 132, 80, 134, 72, 134, 69, 134, 65, 138, 64, 138, 58, 137, 56, 133, 52, 133, 51, 133, 48, 133, 44, 133, 41, 131, 38, 130, 35, 130, 32, 127, 30, 127, 27, 127, 24, 127, 24, 126, 23, 124, 20, 122, 17, 122, 16, 118, 15, 116, 15, 110, 18, 108, 20, 102, 24, 97, 28, 102, 28, 98, 26, 97, 28, 94, 27, 85, 29, 79, 32, 76, 39, 70, 44, 66, 48, 65, 53, 61, 53, 58, 51, 54, 54, 54, 52, 48, 51, 43, 48, 42, 49, 38, 48, 34, 51, 30, 53, 33, 58, 30, 61, 30, 60, 27, 64, 26, 68, 24, 74, 24, 80, 24, 85, 26, 92, 26, 96, 29, 103, 32, 109, 33, 112, 37, 116, 37, 120, 37, 124, 35, 126, 35, 128, 38, 132, 38, 134, 41, 138, 38, 140, 36, 142, 40, 144, 43, 145, 41, 149, 41, 155, 41, 159, 41, 161, 46, 165, 46, 164, 42, 164, 39, 164, 34, 167, 30, 173, 24, 178, 24, 184, 24, 189, 26, 195, 21, 195, 20, 199, 20, 203, 20, 207, 17, 211, 17, 216, 17, 218, 16, 222, 22, 225, 27, 228, 31, 226, 34, 224, 34, 226, 39, 228, 43, 230, 46, 236, 46, 242, 46, 243, 50, 245, 50, 247, 54, 247, 56, 248, 60, 248, 65, 253, 66, 255, 64, 260, 64, 264, 67, 268, 71, 272, 66, 275, 66, 281, 61, 285, 66, 286, 70, 292, 74, 294, 74, 296, 74, 296, 71, 301, 74, 307, 74, 311, 78, 315, 74, 315, 77, 319, 77, 322, 82, 328, 82, 331, 81, 331, 84, 333, 86, 333, 90, 330, 95, 326, 98, 328, 99, 332, 98, 333, 101, 331, 104, 329, 104, 327, 106, 329, 111, 332, 116, 333, 119, 333, 122, 332, 126, 332, 130, 327, 130, 321, 130, 317, 130, 315, 134, 312, 134, 308, 138, 306, 138, 306, 144, 306, 149, 306, 152, 301, 152, 297, 154, 295, 154, 292, 154, 292, 158, 288, 158, 283, 162, 281, 164, 279, 163, 276, 163, 273, 166, 272, 169, 268, 168, 265, 170, 260, 172, 256, 176, 252, 176, 248, 181, 246, 182, 246, 189, 246, 194, 248, 197, 250, 198, 252, 200, 252, 203, 254, 205, 260, 205, 264, 202, 267, 202, 269, 202, 272, 199, 280, 199, 278, 202, 278, 207, 278, 211, 276, 211, 272, 213, 268, 213, 265, 213, 264, 211, 262, 210, 260, 210, 257, 212, 257, 214, 255, 217, 253, 217, 253, 221, 249, 220, 247, 220, 243, 222, 240, 223, 239, 226, 234, 231, 229, 231, 224, 231, 219, 227, 220, 227, 222, 224, 222, 222, 222, 219, 224, 217, 222, 214, 220, 212, 217, 210, 215, 210, 211, 209, 208, 206, 202, 209, 202, 205, 206, 202, 211, 198, 216, 195, 220, 192, 224, 192, 221, 186, 218, 186, 214, 185, 208, 185, 204, 186, 200, 186, 193, 183, 190, 182, 188, 182, 190, 178, 186, 178, 184, 174, 182, 171, 178, 171, 173, 174, 169, 174, 169, 175, 169, 179, 167, 182, 164, 186, 160, 192, 155, 195, 152, 198, 150, 198, 148, 198, 148, 202, 151, 208, 148, 210, 146, 208, 144, 205, 140, 205, 137, 208, 132, 208, 132, 210, 127, 210, 124, 210, 120, 206, 120, 202, 123, 202, 124, 201, 124, 198, 128, 195, 131, 191, 133, 187, 135, 183, 130, 203, 129, 208, 123, 203, 129, 203, 129, 198, 133, 198, 136, 200, 142, 200, 143, 199, 143, 197, 137, 196, 136, 194, 133, 194, 136, 186, 136, 182, 141, 186, 144, 186, 150, 186, 150, 190, 155, 190, 159, 188, 156, 182, 151, 182, 144, 182, 164, 176, 161, 177, 157, 177, 166, 176, 168, 165, 175, 167, 180, 167, 188, 159, 195, 164, 195, 162, 187, 162, 178, 163, 173, 166, 168, 170, 156, 170, 157, 165, 164, 165, 164, 161, 170, 159, 167, 158, 159, 154, 149, 151, 145, 145, 145, 138, 152, 138, 152, 146, 159, 146, 165, 153, 176, 153, 180, 153, 187, 153, 194, 153, 202, 153, 202, 158, 197, 158, 193, 158, 193, 142, 180, 142, 171, 142, 163, 135, 176, 135, 186, 139, 201, 139, 206, 139, 205, 147, 205, 160, 198, 160, 206, 174, 205, 178, 196, 178, 196, 182, 202, 182, 206, 181, 209, 181, 215, 181, 222, 181, 230, 177, 238, 175, 241, 175, 237, 175, 237, 168, 237, 161, 232, 156, 231, 162, 225, 166, 217, 169, 210, 173, 224, 173, 227, 173, 235, 175, 237, 178, 228, 192, 222, 199, 216, 199, 211, 204, 205, 206, 219, 207, 222, 211, 229, 214, 236, 214, 244, 211, 247, 211, 268, 206, 277, 201, 279, 201, 281, 202, 278, 202, 242, 178, 236, 170, 236, 162, 255, 162, 251, 156, 240, 156, 253, 152, 261, 152, 277, 157, 268, 151, 255, 143, 260, 142, 267, 145, 271, 149, 273, 154, 258, 146, 257, 131, 256, 134, 248, 137, 260, 137, 260, 134, 271, 137, 276, 138, 276, 144, 289, 144, 285, 150, 294, 150, 298, 149, 301, 145, 292, 145, 282, 134, 276, 134, 283, 127, 282, 116, 277, 113, 283, 113, 288, 106, 296, 106, 297, 113, 297, 118, 298, 118, 310, 122, 310, 128, 300, 130, 300, 140, 292, 129, 292, 114, 283, 122, 289, 122, 299, 122, 299, 134, 294, 134, 288, 124, 314, 121, 311, 113, 308, 110, 304, 96, 299, 90, 299, 82, 305, 87, 309, 94, 311, 101, 312, 102, 314, 107, 320, 112, 320, 115, 326, 116, 323, 109, 321, 102, 321, 94, 321, 90, 328, 90, 328, 88, 316, 88, 316, 84, 307, 84, 290, 77, 289, 88, 289, 97, 278, 97, 268, 106, 268, 110, 261, 105, 255, 103, 244, 103, 252, 100, 252, 91, 252, 82, 242, 78, 252, 78, 259, 78, 264, 87, 267, 92, 272, 91, 272, 83, 264, 83, 260, 79, 276, 79, 283, 84, 283, 94, 289, 94, 284, 86, 272, 77, 253, 110, 248, 110, 239, 110, 234, 114, 222, 125, 219, 127, 219, 131, 219, 138, 219, 141, 224, 139, 224, 135, 225, 130, 232, 136, 240, 138, 237, 131, 237, 118, 248, 120, 256, 122, 262, 127, 255, 118, 245, 110, 207, 129, 199, 134, 195, 134, 188, 130, 180, 130, 165, 129, 156, 129, 165, 128, 173, 125, 185, 126, 193, 126, 201, 124, 204, 123, 208, 116, 214, 114, 207, 114, 196, 114, 183, 121, 183, 111, 189, 117, 196, 112, 172, 126, 164, 126, 159, 114, 174, 106, 186, 106, 192, 105, 184, 105, 184, 96, 173, 96, 163, 111, 159, 110, 152, 110, 168, 110, 171, 106, 183, 98, 193, 101, 219, 96, 225, 97, 225, 104, 232, 92, 240, 92, 237, 86, 229, 86, 216, 88, 214, 79, 203, 79, 203, 75, 212, 75, 221, 75, 229, 80, 230, 89, 217, 88, 217, 77, 228, 77, 228, 69, 235, 71, 240, 71, 244, 66, 236, 54, 236, 62, 232, 68, 229, 61, 216, 61, 212, 58, 212, 47, 212, 39, 214, 28, 215, 48, 225, 55, 236, 55, 202, 65, 202, 54, 202, 44, 202, 24, 198, 32, 199, 38, 192, 38, 185, 38, 174, 42, 174, 48, 178, 51, 184, 51, 194, 55, 191, 68, 182, 68, 174, 69, 167, 67, 153, 59, 153, 49, 147, 49, 152, 58, 152, 74, 154, 83, 161, 83, 165, 88, 153, 97, 153, 89, 152, 82, 168, 88, 168, 101, 156, 102, 156, 119, 173, 110, 184, 110, 177, 106, 160, 106, 145, 125, 137, 122, 131, 120, 124, 120, 122, 118, 113, 118, 114, 111, 129, 111, 140, 110, 143, 106, 137, 102, 127, 102, 119, 98, 126, 93, 139, 93, 139, 99, 141, 95, 128, 89, 118, 74, 128, 76, 135, 76, 141, 83, 141, 71, 137, 61, 137, 50, 129, 50, 118, 50, 109, 52, 112, 61, 123, 60, 134, 60, 129, 76, 121, 67, 124, 76, 123, 76, 111, 74, 128, 73, 109, 83, 109, 94, 105, 103, 102, 118, 92, 113, 98, 105, 99, 93, 94, 93, 94, 81, 99, 81, 100, 73, 100, 89, 100, 60, 100, 55, 105, 37, 101, 34, 93, 37, 90, 37, 90, 49, 99, 49, 88, 68, 80, 68, 78, 64, 88, 62, 86, 77, 76, 89, 71, 91, 71, 106, 78, 106, 82, 118, 84, 110, 71, 104, 76, 103, 76, 91, 78, 83, 85, 89, 83, 103, 83, 119, 76, 130, 62, 130, 68, 127, 74, 126, 83, 123, 62, 123, 56, 123, 59, 129, 59, 120, 49, 110, 46, 106, 56, 100, 62, 94, 62, 109, 72, 112, 67, 112, 57, 112, 61, 122, 60, 102, 52, 125, 44, 121, 36, 114, 32, 110, 20, 110, 22, 118, 35, 118, 44, 124, 32, 119, 22, 111, 44, 96, 36, 106, 36, 94, 32, 94, 35, 83, 44, 91, 52, 91, 52, 80, 59, 80, 62, 76, 62, 70, 47, 78, 55, 75, 64, 71, 64, 60, 58, 53, 58, 43, 65, 43, 65, 60, 76, 52, 73, 38, 76, 36, 93, 48, 89, 39, 99, 40, 98, 50, 94, 63, 117, 63, 131, 67, 131, 74, 142, 78, 140, 61, 124, 58, 124, 48, 136, 55, 236, 200, 228, 200, 226, 192, 232, 198, 238, 210, 248, 210, 236, 220, 230, 223, 230, 213, 175, 32, 172, 32, 171, 38, 184, 30};
+ // clang-format on
+ validate(coords);
+}
diff --git a/test/main.test.cpp b/test/main.test.cpp
new file mode 100644
index 0000000..b3143fb
--- /dev/null
+++ b/test/main.test.cpp
@@ -0,0 +1,2 @@
+#define CATCH_CONFIG_MAIN
+#include <catch.hpp>
diff --git a/test-files/map-25p-epsg-3857.geojson b/test/test-files/map-25p-epsg-3857.geojson
similarity index 100%
rename from test-files/map-25p-epsg-3857.geojson
rename to test/test-files/map-25p-epsg-3857.geojson
diff --git a/test-files/osm-nodes-45331-epsg-3857.geojson b/test/test-files/osm-nodes-45331-epsg-3857.geojson
similarity index 100%
rename from test-files/osm-nodes-45331-epsg-3857.geojson
rename to test/test-files/osm-nodes-45331-epsg-3857.geojson
diff --git a/test-files/osm-nodes-45331-triangles.json b/test/test-files/osm-nodes-45331-triangles.json
similarity index 100%
rename from test-files/osm-nodes-45331-triangles.json
rename to test/test-files/osm-nodes-45331-triangles.json
diff --git a/test-files/playgrounds-1356-epsg-3857.geojson b/test/test-files/playgrounds-1356-epsg-3857.geojson
similarity index 100%
rename from test-files/playgrounds-1356-epsg-3857.geojson
rename to test/test-files/playgrounds-1356-epsg-3857.geojson
diff --git a/test-files/playgrounds-1356-triangles.json b/test/test-files/playgrounds-1356-triangles.json
similarity index 100%
rename from test-files/playgrounds-1356-triangles.json
rename to test/test-files/playgrounds-1356-triangles.json