Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for generating 2D discrete fields #22

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
24 changes: 20 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.11)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules")

project(Discregrid)

# Visual studio solution directories.
set_property(GLOBAL PROPERTY USE_FOLDERS on)

# Require C++11 compiler
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 2D SDF generation requires Clipper2 Library by AngusJohnson
option(USE_CLIPPER2 "Use Clipper2 library to enable 2D SDF generation" OFF)

if (USE_CLIPPER2)
# Require C++17 compiler as Clipper2 does
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
FetchContent_Declare(
clipper2
GIT_REPOSITORY https://github.com/AngusJohnson/Clipper2.git
GIT_TAG 81b01d2acbad7b06fb28df4fbfbd7228519b7905
SOURCE_SUBDIR CPP
)
FetchContent_MakeAvailable(clipper2)
else()
# Require C++11 compiler
set(CMAKE_CXX_STANDARD 11)
endif()

set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Enable simultaneous compilation of source files.
if(MSVC)
Expand Down
Binary file added Dragon2D.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
**Figure 1**: Left: Slice of a three-dimensional discrete signed distance field of the Stanford dragon. Right: Density map for SPH boundary handling of Stanford dragon.

**Discregrid** is a static C++ library for the parallel discretization of (preferably smooth) functions on regular grids.
The library generates a (cubic) polynomial discretization given a box-shaped domain, a grid resolution, and a function that maps a three-dimensional position in space to a real scalar value.
The library generates a (cubic) polynomial discretization given a box-shaped domain, a grid resolution, and a function that maps a two- or three-dimensional position in space to a real scalar value.
Isoparametric cubic polynomials of Serendipity type for the cell-wise discretization are employed.
The coefficient vector for the discrete polynomial basis is computed using regular sampling of the input function at the higher-order grid's nodes.
The algorithm to generate the discretization is moreover *fully parallelized* using OpenMP and especially well-suited for the discretization of signed distance functions.
The library moreover provides the functionality to serialize and deserialize the a generated discrete grid.
Discregrid ships with [TriangleMeshDistance](https://github.com/InteractiveComputerGraphics/TriangleMeshDistance) to directly provide the capability to compute and discretize signed distance fields to triangle meshes.

![](Dragon2D.png)
**Figure 2**: Left: 2D-Polygon representation of the Stanford dragon. Right: Two-dimensional discrete signed distance field of the Stanford dragon.

Besides the library, the project includes three executable programs that serve the following purposes:
* *GenerateSDF*: Computes a discrete (cubic) signed distance field from a triangle mesh in OBJ format.
* *DiscreteFieldToBitmap*: Generates an image in bitmap format of a two-dimensional slice of a previously computed discretization.
Expand All @@ -26,9 +29,12 @@ Besides the library, the project includes three executable programs that serve t

## Build Instructions

This project is based on [CMake](https://cmake.org/). Simply generate project, Makefiles, etc. using [CMake](https://cmake.org/) and compile the project with the compiler of your choice. The code was tested with the following configurations:
- Windows 10 64-bit, CMake 3.8, Visual Studio 2017
- Debian 9 64-bit, CMake 3.8, GCC 6.3.
This project is based on [CMake](https://cmake.org/). Simply generate project, Makefiles, etc. using [CMake](https://cmake.org/) and compile the project with the compiler of your choice. The minimum required version of CMake for this project is 3.11. The code was tested with the following configurations:
- Windows 10 64-bit, CMake 3.23.1, Visual Studio 2017

If the option USE_CLIPPER2 is active in CMake the [Clipper2](https://github.com/AngusJohnson/Clipper2) library is automatically downloaded and the following two executable programs can also be generated:
* *GenerateSDF2D*: Computes a discrete (cubic) 2D signed distance field from a triangle mesh in OBJ format. Clipper2 is used to convert the triangle mesh to a 2D polygon before discretization, as seen on the left in Figure 2.
* *DiscreteFieldToBitmap2D*: Generates an image in bitmap format of a previously computed 2D discretization.

## Usage
In order to use the library, the main header has to be included and the static library has to be compiled and linked against the client program.
Expand Down
5 changes: 5 additions & 0 deletions cmd/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
add_subdirectory(generate_sdf)
add_subdirectory(discrete_field_to_bitmap)
add_subdirectory(generate_density_map)

if (USE_CLIPPER2)
add_subdirectory(generate_sdf_2d)
add_subdirectory(discrete_field_to_bitmap_2d)
endif()
2 changes: 0 additions & 2 deletions cmd/discrete_field_to_bitmap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ endif()

add_executable(DiscreteFieldToBitmap
main.cpp
bmp_file.hpp
bmp_file.cpp
)

add_dependencies(DiscreteFieldToBitmap
Expand Down
2 changes: 1 addition & 1 deletion cmd/discrete_field_to_bitmap/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <iostream>
#include <array>

#include "bmp_file.hpp"
#include "Discregrid/utility/bmp_file.hpp"

using namespace Eigen;

Expand Down
45 changes: 45 additions & 0 deletions cmd/discrete_field_to_bitmap_2d/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Eigen library.
find_package(Eigen3 REQUIRED)

# Set include directories.
include_directories(
../../extern
../../discregrid/include
${EIGEN3_INCLUDE_DIR}
)


if(WIN32)
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif(WIN32)

if ( CMAKE_COMPILER_IS_GNUCC )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar")
endif ( CMAKE_COMPILER_IS_GNUCC )

# OpenMP support.
find_package(OpenMP REQUIRED)
if(OPENMP_FOUND)
if (CMAKE_VERSION VERSION_GREATER "3.8")
link_libraries(OpenMP::OpenMP_CXX)
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
endif()
endif()

add_executable(DiscreteFieldToBitmap2D
main.cpp
)

add_dependencies(DiscreteFieldToBitmap2D
Discregrid
)

target_link_libraries(DiscreteFieldToBitmap2D
Discregrid
)

set_target_properties(DiscreteFieldToBitmap2D PROPERTIES FOLDER Cmd)
216 changes: 216 additions & 0 deletions cmd/discrete_field_to_bitmap_2d/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@

#include <Discregrid/All>
#include <Eigen/Dense>
#include <cxxopts/cxxopts.hpp>

#include <string>
#include <iostream>
#include <array>

#include "Discregrid/utility/bmp_file.hpp"

using namespace Eigen;

namespace
{
std::array<unsigned char, 3u> doubleToGreenBlueInverse(double v)
{
if (v >= 0.0)
{
return {{0u, static_cast<unsigned char>(std::min(std::max(255.0 * (1.0 - v), 0.0), 255.0)), 0u}};
}
return {{0u, 0u, static_cast<unsigned char>(std::min(std::max(255.0 * (1.0 + v), 0.0), 255.0))}};
}

std::array<unsigned char, 3u> doubleToRedSequential(double v)
{
return {{static_cast<unsigned char>(std::min(std::max(255.0 * v, 0.0), 255.0)), 0u, 0u}};
}

std::array<unsigned char, 3u> doubleTo5ColourHeatmap(double v, double minValue, double maxValue)
{
const double normalised = (v - minValue) / (maxValue - minValue);

constexpr double h1 = 0;
constexpr double h2 = 0.66667;
const double h = (1.0 - normalised) * h1 + normalised * h2;
constexpr double s = 1.0;
constexpr double l = 0.5;

auto hue2rgb = [](double p, double q, double t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6.0) return p + (q - p) * 6 * t;
if(t < 1/2.0) return q;
if(t < 2/3.0) return p + (q - p) * (2/3.0 - t) * 6;
return p;
};

constexpr double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
constexpr double p = 2 * l - q;

return {
static_cast<unsigned char>(hue2rgb(p, q, h + 1 / 3.0) * 255),
static_cast<unsigned char>(hue2rgb(p, q, h) * 255),
static_cast<unsigned char>(hue2rgb(p, q, h - 1 / 3.0) * 255)
};
}

}

int main(int argc, char* argv[])
{
cxxopts::Options options(argv[0], "Transforms a slice of a discrete 2D-SDF to a bitmap image.");
options.positional_help("[input 2D-SDF file (.cdf2d)]");

options.add_options()
("h,help", "Prints this help text")
("f,field_id", "ID in which the SDF to export is stored.", cxxopts::value<unsigned int>()->default_value("0"))
("s,samples", "Number of samples in width direction", cxxopts::value<unsigned int>()->default_value("1024"))
("o,output", "Output (in bmp format)", cxxopts::value<std::string>()->default_value(""))
("c,colormap", "Color map options: redsequential (rs), green blue inverse diverging (gb) (suitable for visualisation of signed distance fields), 5 colour heatmap (hm) (suitable for visualisation of differences/errors)", cxxopts::value<std::string>()->default_value("gb"))
("input", "SDF file", cxxopts::value<std::vector<std::string>>())
;

try
{
options.parse_positional("input");
auto result = options.parse(argc, argv);

if (result.count("help"))
{
std::cout << options.help() << std::endl;
std::cout << std::endl << std::endl << "Example: SDFToBitmap2D -p xz file.sdf2d" << std::endl;
exit(0);
}
if (!result.count("input"))
{
std::cout << "ERROR: No input file given." << std::endl;
std::cout << options.help() << std::endl;
std::cout << std::endl << std::endl << "Example: SDFToBitmap2D -p xz file.sdf2d" << std::endl;
exit(1);
}

auto sdf = std::unique_ptr<Discregrid::DiscreteGrid2D>{};

auto filename = result["input"].as<std::vector<std::string>>().front();
auto lastindex = filename.find_last_of(".");
auto extension = filename.substr(lastindex + 1, filename.length() - lastindex);

std::cout << "Load SDF...";
if (extension == "cdf2d")
{
sdf = std::make_unique<Discregrid::CubicLagrangeDiscreteGrid2D>(filename);
}
else
{
std::cout << "ERROR: Input file must be a '.sdf2d' file specifically." << std::endl;
std::cout << options.help() << std::endl;
std::cout << std::endl << std::endl << "Example: SDFToBitmap2D -p xz file.sdf2D" << std::endl;
exit(1);
}
std::cout << "DONE" << std::endl;

auto const& domain = sdf->domain();
auto diag = domain.diagonal().eval();

auto dir = Vector2i::Zero().eval();
dir(1) = 1;

auto xsamples = result["s"].as<unsigned int>();
auto ysamples = static_cast<unsigned int>(std::round(diag(dir(1)) / diag(dir(0)) * static_cast<double>(xsamples)));

auto xwidth = diag(dir(0)) / xsamples;
auto ywidth = diag(dir(1)) / ysamples;

auto data = std::vector<double>{};
data.resize(xsamples * ysamples);

auto field_id = result["f"].as<unsigned int>();

std::cout << "Sample field...";
#pragma omp parallel for
for (int k = 0; k < static_cast<int>(xsamples * ysamples); ++k)
{
auto i = k % xsamples;
auto j = k / xsamples;

auto xr = static_cast<double>(i) / static_cast<double>(xsamples);
auto yr = static_cast<double>(j) / static_cast<double>(ysamples);

auto x = domain.min()(dir(0)) + xr * diag(dir(0)) + 0.5 * xwidth;
auto y = domain.min()(dir(1)) + yr * diag(dir(1)) + 0.5 * ywidth;

auto sample = Vector2d{};
sample(dir(0)) = x;
sample(dir(1)) = y;

data[k] = sdf->interpolate(field_id, sample);
if (data[k] == std::numeric_limits<double>::max())
{
data[k] = 0.0;
}
}

std::cout << "DONE" << std::endl;

auto min_v = *std::min_element(data.begin(), data.end());
auto max_v = *std::max_element(data.begin(), data.end());

auto out_file = result["o"].as<std::string>();
if (out_file == "")
{
out_file = filename;
if (out_file.find(".") != std::string::npos)
{
auto lastindex = out_file.find_last_of(".");
out_file = out_file.substr(0, lastindex);
}
out_file += ".bmp";
}

std::cout << "Ouput file: " << out_file << std::endl;

std::cout << "Export BMP...";
std::transform(data.begin(), data.end(), data.begin(), [&max_v, &min_v](double v) {return v >= 0.0 ? v / std::abs(max_v) : v / std::abs(min_v); });

auto pixels = std::vector<std::array<unsigned char, 3u>>(data.size());

auto cm = result["c"].as<std::string>();
if (cm != "gb" && cm != "rs" && cm != "hm")
{
std::cerr << "WARNING: Unknown color map option. Fallback to mode 'gb'." << std::endl;
}

if (cm == "gb")
std::transform(data.begin(), data.end(), pixels.begin(), doubleToGreenBlueInverse);
else if (cm == "rs")
std::transform(data.begin(), data.end(), pixels.begin(), doubleToRedSequential);
else if (cm == "hm")
{
const auto min_max = std::minmax_element(
data.begin(), data.end());
const auto min = *min_max.first;
const auto max = *min_max.second;
const auto& heatmap = [min, max](double v) { return doubleTo5ColourHeatmap(v, min, max); };
std::transform(data.begin(), data.end(), pixels.begin(), heatmap);
}


BmpReaderWriter::saveFile(out_file.c_str(), xsamples, ysamples, &pixels.front()[0]);
std::cout << "DONE" << std::endl;

std::cout << std::endl << "Statistics:" << std::endl;
std::cout << "\tdomain = " << domain.min().transpose() << ", " << domain.max().transpose() << std::endl;
std::cout << "\tmin value = " << min_v << std::endl;
std::cout << "\tmax value = " << max_v << std::endl;
std::cout << "\tbmp resolution = " << xsamples << " x " << ysamples << std::endl;
}
catch (cxxopts::OptionException const& e)
{
std::cout << "error parsing options: " << e.what() << std::endl;
exit(1);
}

return 0;
}
Loading