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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ cmake_install.cmake
CMakeFiles
CMakeCache.txt
build/*

decimater
39 changes: 33 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,26 +1,53 @@
cmake_minimum_required(VERSION 2.8.6)
cmake_minimum_required(VERSION 3.10)
project(SeamAwareDecimater)

SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

## We need Eigen
find_package(EIGEN REQUIRED)
include_directories( "${EIGEN_INCLUDE_DIR}" )
find_package(Eigen REQUIRED)
include_directories( "${Eigen_INCLUDE_DIR}" )

## We need libigl
find_package(LIBIGL REQUIRED)
include_directories( "${LIBIGL_INCLUDE_DIR}" )

find_package(OpenMP)
if(OpenMP_CXX_FOUND)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
else()
# Try to find Homebrew libomp on macOS
if(APPLE)
execute_process(COMMAND brew --prefix libomp OUTPUT_VARIABLE LIBOMP_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE)
if(LIBOMP_PREFIX)
set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I${LIBOMP_PREFIX}/include")
set(OpenMP_CXX_LIBRARIES "-L${LIBOMP_PREFIX}/lib -lomp")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
set(LIBIGL_LIBRARIES ${LIBIGL_LIBRARIES} ${OpenMP_CXX_LIBRARIES})
message(STATUS "Found Homebrew libomp at ${LIBOMP_PREFIX}")
endif()
endif()
endif()

## We don't have/want MOSEK
add_definitions(-DIGL_NO_MOSEK)

link_directories(
/usr/local/lib
${EIGEN_DIRS}
)

## We need C++11. Put this directive after CGAL's include.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g " )
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

option(ENABLE_TIMING "Enable performance timing" OFF)
if(ENABLE_TIMING)
add_definitions(-DENABLE_TIMING)
endif()

# Set a default build type if none was specified
if(NOT CMAKE_BUILD_TYPE)
message(STATUS "Setting build type to 'Release' as none was specified.")
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, options are: Debug, Release, RelWithDebInfo, MinSizeRel." FORCE)
endif()

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_compile_options(-stdlib=libc++)
Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,24 @@ This project uses C++ 11, and it depends on:
- [eigen](http://eigen.tuxfamily.org/) (e.g. `brew install eigen`)

### Compile this project
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make
You can use the provided build script:
```bash
./build.sh
```

Or compile manually:
```bash
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make
```

To enable performance timing, use the `ENABLE_TIMING` CMake option:
```bash
cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_TIMING=ON ..
make
```

### Run this project
./decimater ../models/animal.obj percent-vertices 50
Expand All @@ -46,4 +60,4 @@ The default strictness is 2.
### Example
The Animal model is decimated to 3% of its original number of vertices. The boundary of its UV parameterization stays.
<img src = "results/extreme_decimation.001.png" width="100%">
<img src = "results/strictness.png" width="100%">
<img src = "results/strictness.png" width="100%">
5 changes: 5 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=Release "$@" ..
make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)
42 changes: 21 additions & 21 deletions cmake/FindEIGEN.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
#
# Once done this will define
#
# EIGEN_FOUND - system has eigen lib with correct version
# EIGEN_INCLUDE_DIR - the eigen include directory
# EIGEN_VERSION - eigen version
# Eigen_FOUND - system has eigen lib with correct version
# Eigen_INCLUDE_DIR - the eigen include directory
# Eigen_VERSION - eigen version

# Copyright (c) 2006, 2007 Montel Laurent, <montel@kde.org>
# Copyright (c) 2008, 2009 Gael Guennebaud, <g.gael@free.fr>
Expand All @@ -30,7 +30,7 @@ if(NOT Eigen_FIND_VERSION)
endif(NOT Eigen_FIND_VERSION)

macro(_eigen3_check_version)
file(READ "${EIGEN_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)
file(READ "${Eigen_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)

string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}")
set(EIGEN_WORLD_VERSION "${CMAKE_MATCH_1}")
Expand All @@ -39,29 +39,29 @@ macro(_eigen3_check_version)
string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}")
set(EIGEN_MINOR_VERSION "${CMAKE_MATCH_1}")

set(EIGEN_VERSION ${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION})
if(${EIGEN_VERSION} VERSION_LESS ${Eigen_FIND_VERSION})
set(EIGEN_VERSION_OK FALSE)
else(${EIGEN_VERSION} VERSION_LESS ${Eigen_FIND_VERSION})
set(EIGEN_VERSION_OK TRUE)
endif(${EIGEN_VERSION} VERSION_LESS ${Eigen_FIND_VERSION})
set(Eigen_VERSION ${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION})
if(${Eigen_VERSION} VERSION_LESS ${Eigen_FIND_VERSION})
set(Eigen_VERSION_OK FALSE)
else(${Eigen_VERSION} VERSION_LESS ${Eigen_FIND_VERSION})
set(Eigen_VERSION_OK TRUE)
endif(${Eigen_VERSION} VERSION_LESS ${Eigen_FIND_VERSION})

if(NOT EIGEN_VERSION_OK)
if(NOT Eigen_VERSION_OK)

message(STATUS "Eigen version ${EIGEN_VERSION} found in ${EIGEN_INCLUDE_DIR}, "
message(STATUS "Eigen version ${Eigen_VERSION} found in ${Eigen_INCLUDE_DIR}, "
"but at least version ${Eigen_FIND_VERSION} is required")
endif(NOT EIGEN_VERSION_OK)
endif(NOT Eigen_VERSION_OK)
endmacro(_eigen3_check_version)

if (EIGEN_INCLUDE_DIRS)
if (Eigen_INCLUDE_DIRS)

# in cache already
_eigen3_check_version()
set(EIGEN_FOUND ${EIGEN_VERSION_OK})
set(Eigen_FOUND ${Eigen_VERSION_OK})

else ()

find_path(EIGEN_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
find_path(Eigen_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
PATHS
${CMAKE_INSTALL_PREFIX}/include
${KDE4_INCLUDE_DIR}
Expand All @@ -76,14 +76,14 @@ else ()
PATH_SUFFIXES eigen3 eigen
)

if(EIGEN_INCLUDE_DIR)
if(Eigen_INCLUDE_DIR)
_eigen3_check_version()
endif(EIGEN_INCLUDE_DIR)
endif(Eigen_INCLUDE_DIR)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Eigen DEFAULT_MSG EIGEN_INCLUDE_DIR EIGEN_VERSION_OK)
find_package_handle_standard_args(Eigen DEFAULT_MSG Eigen_INCLUDE_DIR Eigen_VERSION_OK)

mark_as_advanced(EIGEN_INCLUDE_DIR)
SET(EIGEN_INCLUDE_DIRS ${EIGEN_INCLUDE_DIR} CACHE PATH "The Eigen include path.")
mark_as_advanced(Eigen_INCLUDE_DIR)
SET(Eigen_INCLUDE_DIRS ${Eigen_INCLUDE_DIR} CACHE PATH "The Eigen include path.")

endif()
102 changes: 73 additions & 29 deletions collapse_edge_seam.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ bool try_collapse_5d_Edge(
Eigen::MatrixXi & EI,
Eigen::MatrixXd & TC, // TODO: Texture coordinates
Eigen::MatrixXi & FT, // TODO: Texture coordinates per face.
Eigen::MatrixXd & V_scaled,
Eigen::MatrixXd & TC_scaled,
EdgeMap & seam_edges, // TODO: A set of edges in V for vertices which lie on edges which should be preserved.
MapV5d & Vmetrics, // TODO: The per-vertex data.
int & a_e1,
int & a_e2)
int & a_e2,
bool preserve_boundaries,
double pos_scale,
double uv_weight)
{
// Assign this to 0 rather than, say, -1 so that deleted elements will get
// draw as degenerate elements at vertex 0 (which should always exist and
Expand All @@ -45,6 +50,43 @@ bool try_collapse_5d_Edge(
const int d = eflip?E(e,0):E(e,1);
const bool collapse_on_seam = contains_edge( seam_edges, s, d );

// If this edge is a boundary edge and we want to preserve them, don't collapse.
if( preserve_boundaries && collapse_on_seam) {
const auto& faces = igl::circulation(e, true, EMAP, EF, EI);
if (faces.size() < 2) return false; // Should not happen on a closed mesh.
// A boundary edge will have one half-edge that has different UVs than the other.
// However, a simpler check is to see if one of the faces is an "infinity" face.
// We can't know the infinity vertex index here easily without more plumbing.
// Instead, we can reject any collapse on a "seam" edge that isn't a UV seam.
// A real UV seam will have a bundle size of 2. A boundary edge, treated as
// a seam, will not be distinguishable here without more info.
// For now, we will prevent all "seam" edges from collapsing if preserve_boundaries is on.
// This will protect UV seams as well, which might be too restrictive.
// A better approach would be to differentiate, but this is a safe starting point.

Bundle bundle = get_half_edge_bundle( e, E, EF, EI, F, FT );
if (bundle.size() < 2) {
// This is a boundary edge for sure.
return false;
}

if(bundle[0].p[0].vi == s) {
assert(bundle[0].p[1].vi == d);
if((bundle[1].p[0].vi == s && bundle[0].p[0].tci == bundle[1].p[0].tci && bundle[0].p[1].tci == bundle[1].p[1].tci) ||
(bundle[1].p[0].vi == d && bundle[0].p[0].tci == bundle[1].p[1].tci && bundle[0].p[1].tci == bundle[1].p[0].tci))
{
return false;
}
} else {
assert(bundle[0].p[0].vi == d && bundle[0].p[1].vi == s);
if((bundle[1].p[0].vi == d && bundle[0].p[0].tci == bundle[1].p[0].tci && bundle[0].p[1].tci == bundle[1].p[1].tci) ||
(bundle[1].p[0].vi == s && bundle[0].p[0].tci == bundle[1].p[1].tci && bundle[0].p[1].tci == bundle[1].p[0].tci))
{
return false;
}
}
}

// If both endpoints are on seams, but there is no seam between them, reject it.
if( seam_edges.count( s ) && seam_edges.count( d ) && !collapse_on_seam ) {
return false;
Expand Down Expand Up @@ -117,45 +159,54 @@ bool try_collapse_5d_Edge(
assert( new_placement.tcs.size() == 2 );
assert( new_placement.metrics.size() == 2 );
// move source and destination to midpoint
V.row(s) = new_placement.p;
V.row(d) = new_placement.p;
V.row(s) = new_placement.p / pos_scale;
V.row(d) = new_placement.p / pos_scale;
V_scaled.row(s) = new_placement.p;
V_scaled.row(d) = new_placement.p;
// Update UV coordinates of the edge endpoints here. If both edge endpoints are on the seam, return false (see above). If one of the edge endpoints is on the seam, we should be able to handle it preserving that endpoint and collapsing the other one.
int he0_ts = bundle[0].p[0].tci;
int he0_td = bundle[0].p[1].tci;
if( bundle[0].p[0].vi == d ) std::swap( he0_ts, he0_td );
TC.row(he0_ts) = new_placement.tcs[0];
TC.row(he0_td) = new_placement.tcs[0];
TC.row(he0_ts) = new_placement.tcs[0] / uv_weight;
TC.row(he0_td) = new_placement.tcs[0] / uv_weight;
TC_scaled.row(he0_ts) = new_placement.tcs[0];
TC_scaled.row(he0_td) = new_placement.tcs[0];
int he1_ts = bundle[1].p[0].tci;
int he1_td = bundle[1].p[1].tci;
if( bundle[1].p[0].vi == d ) std::swap( he1_ts, he1_td );
TC.row(he1_ts) = new_placement.tcs[1];
TC.row(he1_td) = new_placement.tcs[1];
TC.row(he1_ts) = new_placement.tcs[1] / uv_weight;
TC.row(he1_td) = new_placement.tcs[1] / uv_weight;
TC_scaled.row(he1_ts) = new_placement.tcs[1];
TC_scaled.row(he1_td) = new_placement.tcs[1];
// Update the per-vertex metric.
// Move the other d metrics to s.
Vmetrics[d].erase(he0_td);
Vmetrics[d].erase(he1_td);
Vmetrics[s].insert(Vmetrics[d].begin(), Vmetrics[d].end());
Vmetrics.erase(d);
Vmetrics[d].clear();
Vmetrics[s][he0_ts] = new_placement.metrics[0];
Vmetrics[s][he1_ts] = new_placement.metrics[1];
}
else {
// if(seam_edges.count( s ) || seam_edges.count( d )) cout << "try to decimate edge attached to seam." << endl;
assert( new_placement.tcs.size() == 1 );
assert( new_placement.metrics.size() == 1 );
// move source and destination to midpoint
V.row(s) = new_placement.p;
V.row(d) = new_placement.p;
V.row(s) = new_placement.p / pos_scale;
V.row(d) = new_placement.p / pos_scale;
V_scaled.row(s) = new_placement.p;
V_scaled.row(d) = new_placement.p;
// Update UV coordinates of the edge endpoints here. If both edge endpoints are on the seam, return false (see above). If one of the edge endpoints is on the seam, we should be able to handle it preserving that endpoint and collapsing the other one.
TC.row(s_tc) = new_placement.tcs[0];
TC.row(d_tc) = new_placement.tcs[0];
TC.row(s_tc) = new_placement.tcs[0] / uv_weight;
TC.row(d_tc) = new_placement.tcs[0] / uv_weight;
TC_scaled.row(s_tc) = new_placement.tcs[0];
TC_scaled.row(d_tc) = new_placement.tcs[0];
// Update the per-vertex metric.
// Move the other d metrics to s.
assert(bundle[0].p[0] == bundle[1].p[0] || bundle[0].p[0] == bundle[1].p[1]);
assert(bundle[0].p[1] == bundle[1].p[0] || bundle[0].p[1] == bundle[1].p[1]);
Vmetrics[d].erase(d_tc);
Vmetrics[s].insert(Vmetrics[d].begin(), Vmetrics[d].end());
Vmetrics.erase(d);
Vmetrics[d].clear();
Vmetrics[s][s_tc] = new_placement.metrics[0];
}

Expand Down Expand Up @@ -318,7 +369,11 @@ bool collapse_edge_with_uv(
std::vector<std::set<std::pair<double,int> >::iterator > & Qit,
std::vector< placement_info_5d > & C,
int & e,
bool test)
bool preserve_boundaries,
double pos_scale,
double uv_weight,
Eigen::MatrixXd & V_scaled,
Eigen::MatrixXd & TC_scaled)
{
using namespace std;
using namespace Eigen;
Expand Down Expand Up @@ -347,12 +402,9 @@ bool collapse_edge_with_uv(

int e1,e2;
const bool collapsed =
try_collapse_5d_Edge(e,C.at(e),V,F,E,EMAP,EF,EI,TC,FT,seam_edges,Vmetrics,e1,e2);
try_collapse_5d_Edge(e,C.at(e),V,F,E,EMAP,EF,EI,TC,FT,V_scaled, TC_scaled,seam_edges,Vmetrics,e1,e2, preserve_boundaries, pos_scale, uv_weight);
if(collapsed)
{
if(test == true ) {
cout << "try collapse succeed." << endl;
}
// Erase the two, other collapsed edges
Q.erase(Qit[e1]);
Qit[e1] = Q.end();
Expand All @@ -378,9 +430,7 @@ bool collapse_edge_with_uv(
}
}
}
if(test == true ) {
cout << "first loop succeed." << endl;
}

for( auto ei : affected_edges )
{
if( E(ei,0) != DUV_COLLAPSE_EDGE_NULL && E(ei,1) != DUV_COLLAPSE_EDGE_NULL )
Expand All @@ -392,20 +442,14 @@ bool collapse_edge_with_uv(
double cost;
placement_info_5d place;
Bundle b = get_half_edge_bundle( ei, E, EF, EI, F, FT );
cost_and_placement_qslim5d_halfedge(b,V,F,TC,FT,seam_edges,Vmetrics,seam_aware_degree,cost,place);
cost_and_placement_qslim5d_halfedge(b,V_scaled,F,TC_scaled,FT,seam_edges,Vmetrics,seam_aware_degree,pos_scale,uv_weight,cost,place);
// Replace in queue
Qit[ei] = Q.insert(std::pair<double,int>(cost,ei)).first;
C.at(ei) = place;
}
}
if(test == true ) {
cout << "second loop succeed." << endl;
}
} else
{
if(test == true ) {
cout << "try collapse failed." << endl;
}
// reinsert with infinite weight (the provided cost function must **not**
// have given this un-collapsable edge inf cost already)
p.first = std::numeric_limits<double>::infinity();
Expand Down
Loading