Skip to content

Commit

Permalink
algorithm: Add alpha wrapping 3d algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
ptitjano committed Oct 24, 2024
1 parent 41c9e59 commit 3a32380
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ file( GLOB_RECURSE SFCGAL_HEADERS "*.h" )
if (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC )
list(REMOVE_ITEM SFCGAL_SOURCES ${CMAKE_SOURCE_DIR}/src/algorithm/alphaShapes.cpp)
list(REMOVE_ITEM SFCGAL_HEADERS ${CMAKE_SOURCE_DIR}/src/algorithm/alphaShapes.h)
list(REMOVE_ITEM SFCGAL_SOURCES ${CMAKE_SOURCE_DIR}/src/algorithm/alphaWrapping3D.cpp)
list(REMOVE_ITEM SFCGAL_HEADERS ${CMAKE_SOURCE_DIR}/src/algorithm/alphaWrapping3D.h)
endif()

file( GLOB_RECURSE SFCGAL_HEADERS_COPIED RELATIVE ${CMAKE_SOURCE_DIR}/src "*.h" )
Expand Down
62 changes: 62 additions & 0 deletions src/algorithm/alphaWrapping3D.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2024-2024, SFCGAL Contributors and Oslandia
// SPDX-License-Identifier: LGPL-2.0-or-later

#include "SFCGAL/algorithm/alphaWrapping3D.h"
#include "SFCGAL/detail/GetPointsVisitor.h"

#include <CGAL/Cartesian_converter.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/alpha_wrap_3.h>

namespace SFCGAL::algorithm {

using InexactKernel = CGAL::Exact_predicates_inexact_constructions_kernel;
using Inexact_Point_3 = InexactKernel::Point_3;
using Mesh = CGAL::Surface_mesh<Inexact_Point_3>;
using ExactMesh = CGAL::Surface_mesh<Kernel::Point_3>;
using EK_to_IK = CGAL::Cartesian_converter<Kernel, InexactKernel>;
using IK_to_EK = CGAL::Cartesian_converter<InexactKernel, Kernel>;

auto
alphaWrapping3D(const Geometry &geom, size_t relativeAlpha,
size_t relativeOffset) -> std::unique_ptr<PolyhedralSurface>
{
if (geom.isEmpty()) {
return std::make_unique<PolyhedralSurface>();
}

// Collect points from geometry
SFCGAL::detail::GetPointsVisitor getPointVisitor;
const_cast<Geometry &>(geom).accept(getPointVisitor);

// Need at least 4 points for 3D alpha wrapping
if (getPointVisitor.points.size() < 4) {
return std::make_unique<PolyhedralSurface>();
}

// Create points vector
EK_to_IK toInexact;
std::vector<Inexact_Point_3> points;
points.reserve(getPointVisitor.points.size());
for (const auto &point : getPointVisitor.points) {
points.push_back(toInexact(point->toPoint_3()));
}

// compute alpha and offset
CGAL::Bbox_3 bbox = CGAL::bbox_3(points.begin(), points.end());
const double diag_length = std::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) +
CGAL::square(bbox.ymax() - bbox.ymin()) +
CGAL::square(bbox.zmax() - bbox.zmin()));
const double alpha = diag_length / static_cast<double>(relativeAlpha);

Mesh wrapMesh;
if (relativeOffset == 0) {
CGAL::alpha_wrap_3(points, alpha, wrapMesh);
} else {
const double offset = diag_length / static_cast<double>(relativeOffset);
CGAL::alpha_wrap_3(points, alpha, offset, wrapMesh);
}

return std::make_unique<PolyhedralSurface>(PolyhedralSurface(wrapMesh));
}
} // namespace SFCGAL::algorithm
51 changes: 51 additions & 0 deletions src/algorithm/alphaWrapping3D.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* SFCGAL
*
* Copyright (C) 2024 SFCGAL Contributors and Oslandia <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, see
<http://www.gnu.org/licenses/>.
*/

#ifndef _SFCGAL_ALGORITHM_ALPHASHAPES3D_H_
#define _SFCGAL_ALGORITHM_ALPHASHAPES3D_H_

#include "SFCGAL/Geometry.h"
#include "SFCGAL/PolyhedralSurface.h"

namespace SFCGAL::algorithm {

/**
* Computes the 3D alpha wrapping of a geometry
* https://doc.cgal.org/latest/Alpha_wrap_3/index.html
* @ingroup public_api
* @since 2.1
* @param geom input geometry
* @param relativeAlpha This parameter is used to determine which features will
* appear in the output A small relativeAlpha will produce an output less
* complex but less faithful to the input
* @param relativeOffset This parameter controls the tightness of the result
* A large relativeOffset parameter will tend to better preserve sharp features
* as projection If this parameter is equal to 0, it is computed from the alpha
* parameter
* @return A PolyhedralSurface representing the 3D alpha wrapping of the
* geometry
*/
SFCGAL_API std::unique_ptr<PolyhedralSurface>
alphaWrapping3D(const Geometry &geom, size_t relativeAlpha,
size_t relativeOffset = 0);

} // namespace SFCGAL::algorithm

#endif
26 changes: 26 additions & 0 deletions src/capi/sfcgal_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#if !_MSC_VER
#include "SFCGAL/algorithm/alphaShapes.h"
#include "SFCGAL/algorithm/alphaWrapping3D.h"
#endif
#include "SFCGAL/algorithm/area.h"
#include "SFCGAL/algorithm/buffer3D.h"
Expand Down Expand Up @@ -1416,6 +1417,31 @@ sfcgal_geometry_optimal_alpha_shapes(const sfcgal_geometry_t *geom,
}
#endif

#if !_MSC_VER
extern "C" auto
sfcgal_geometry_alpha_wrapping_3d(const sfcgal_geometry_t *geom,
size_t relativeAlpha, size_t relativeOffset)
-> sfcgal_geometry_t *
{
const auto *inputGeom = reinterpret_cast<const SFCGAL::Geometry *>(geom);
std::unique_ptr<SFCGAL::Geometry> result;

try {
result = SFCGAL::algorithm::alphaWrapping3D(
inputGeom->as<const SFCGAL::Geometry>(), relativeAlpha, relativeOffset);
} catch (std::exception &e) {
SFCGAL_WARNING("During wrapping_3d(A, %g %g):", relativeAlpha,
relativeOffset);
SFCGAL_WARNING(" with A: %s",
((const SFCGAL::Geometry *)(geom))->asText().c_str());
SFCGAL_ERROR("%s", e.what());
return nullptr;
}

return result.release();
}
#endif

extern "C" auto
sfcgal_y_monotone_partition_2(const sfcgal_geometry_t *geom)
-> sfcgal_geometry_t *
Expand Down
25 changes: 25 additions & 0 deletions src/capi/sfcgal_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,31 @@ sfcgal_geometry_optimal_alpha_shapes(const sfcgal_geometry_t *geom,
bool allow_holes, size_t nb_components);
#endif

#if !_MSC_VER
/**
* Returns the 3D alpha wrapping of a geometry
* @pre isValid(geom) == true
* @pre relativeAlpa >= 0
* @pre relativeOffset >= 0
* @post isValid(return) == true
* @param geom input geometry
* @param relativeAlpha This parameter is used to determine which features will
* appear in the output A small relativeAlpha will produce an output less
* complex but less faithful to the input
* @param relativeOffset This parameter controls the tightness of the result
* A large relativeOffset parameter will tend to better preserve sharp features
* as projection If this parameter is equal to 0, it is computed from the alpha
* parameter
* @return A PolyhedralSurface representing the 3D alpha wrapping of the
* geometry
* @ingroup capi
*/
SFCGAL_API sfcgal_geometry_t *
sfcgal_geometry_alpha_wrapping_3d(const sfcgal_geometry_t *geom,
size_t relative_alpha,
size_t relative_offset);
#endif

/**
* Returns the y monotone partition of a geometry (polygon without hole)
* @pre isValid(geom) == true
Expand Down
1 change: 1 addition & 0 deletions test/data/bunny1000AlphaWrapping20Wkt.txt

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/data/bunny1000Wkt.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ file( GLOB_RECURSE SFCGAL_UNIT_TEST_SOURCES *.cpp )

if (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC )
list(REMOVE_ITEM SFCGAL_UNIT_TEST_SOURCES ${CMAKE_SOURCE_DIR}/test/unit/SFCGAL/algorithm/AlphaShapesTest.cpp)
list(REMOVE_ITEM SFCGAL_UNIT_TEST_SOURCES ${CMAKE_SOURCE_DIR}/test/unit/SFCGAL/algorithm/AlphaWrapping3D.cpp)
endif()

add_executable( unit-test-SFCGAL ${SFCGAL_UNIT_TEST_SOURCES} )
Expand All @@ -24,4 +25,3 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug" )
else()
add_test( unit-test ${CMAKE_CURRENT_BINARY_DIR}/unit-test-SFCGAL --auto_start_dbg=y --log_level=all)
endif()

79 changes: 79 additions & 0 deletions test/unit/SFCGAL/algorithm/AlphaWrapping3DTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* SFCGAL
*
* Copyright (C) 2012-2013 Oslandia <[email protected]>
* Copyright (C) 2012-2013 IGN (http://www.ign.fr)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, see
<http://www.gnu.org/licenses/>.
*/
#include <boost/format/parsing.hpp>
#include <boost/test/tools/old/interface.hpp>
#include <boost/test/unit_test.hpp>

#include "SFCGAL/GeometryCollection.h"
#include "SFCGAL/Polygon.h"
#include "SFCGAL/algorithm/alphaWrapping3D.h"
#include "SFCGAL/algorithm/covers.h"
#include "SFCGAL/io/wkt.h"

using namespace SFCGAL;

#include "../../../test_config.h"
// always after CGAL
using namespace boost::unit_test;

BOOST_AUTO_TEST_SUITE(SFCGAL_algorithm_AlphaWrapping3DTest)

// algorithm::alphaWrapping3D


BOOST_AUTO_TEST_CASE(testAlphaWrapping3D_Empty)
{
GeometryCollection emptyCollection;
emptyCollection.addGeometry(Polygon());
emptyCollection.addGeometry(Polygon());
std::unique_ptr<Geometry> emptyAlphaWrapping3D (algorithm::alphaWrapping3D(emptyCollection, 300, 5000));
BOOST_CHECK(emptyAlphaWrapping3D->isEmpty());
}

BOOST_AUTO_TEST_CASE(testAlphaWrapping3D_MultiPoint)
{
std::string inputData(SFCGAL_TEST_DIRECTORY);
inputData += "/data/bunny1000Wkt.txt";
std::ifstream bunnyFSInput(inputData.c_str());
BOOST_REQUIRE(bunnyFSInput.good());
std::ostringstream inputWkt;
inputWkt << bunnyFSInput.rdbuf();

std::unique_ptr<Geometry> inputGeom(io::readWkt(inputWkt.str()));
BOOST_REQUIRE(inputGeom->is3D());

std::unique_ptr<Geometry> alphaWrappingResult(algorithm::alphaWrapping3D(inputGeom->as<const SFCGAL::Geometry>(), 20));

std::string resultData(SFCGAL_TEST_DIRECTORY);
resultData += "/data/bunny1000AlphaWrapping20Wkt.txt";
std::ifstream bunnyFSResult(resultData.c_str());
BOOST_REQUIRE(bunnyFSResult.good());
std::ostringstream resultWkt;
resultWkt << bunnyFSResult.rdbuf();

std::unique_ptr<Geometry> alphaWrappingExpectedGeom(io::readWkt(resultWkt.str()));
BOOST_REQUIRE(alphaWrappingExpectedGeom->is3D());

BOOST_CHECK(algorithm::covers3D(*alphaWrappingResult, *alphaWrappingExpectedGeom));
}


BOOST_AUTO_TEST_SUITE_END()
35 changes: 35 additions & 0 deletions test/unit/SFCGAL/capi/sfcgal_cTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
* License along with this library; if not, see
<http://www.gnu.org/licenses/>.
*/

#include "../../../test_config.h"

#include "SFCGAL/capi/sfcgal_c.h"
#include "SFCGAL/GeometryCollection.h"
#include "SFCGAL/LineString.h"
Expand Down Expand Up @@ -475,4 +478,36 @@ BOOST_AUTO_TEST_CASE(testSolidSetExteriorShell)
BOOST_CHECK(sfcgal_geometry_covers_3d(sfcgal_solid_shell_n(solid.get(), 0), shell1.get()));
}

#if !_MSC_VER
BOOST_AUTO_TEST_CASE(testAlphaWrapping3DTest)
{
sfcgal_set_error_handlers(printf, on_error);

std::string inputData(SFCGAL_TEST_DIRECTORY);
inputData += "/data/bunny1000Wkt.txt";
std::ifstream bunnyFSInput(inputData.c_str());
BOOST_REQUIRE(bunnyFSInput.good());
std::ostringstream inputWkt;
inputWkt << bunnyFSInput.rdbuf();

std::unique_ptr<Geometry> const geomInput(io::readWkt(inputWkt.str()));
BOOST_REQUIRE(geomInput->is3D());

sfcgal_geometry_t *geomAlphaWrapping = sfcgal_geometry_alpha_wrapping_3d(geomInput.get(), 20, 0);

std::string resultData(SFCGAL_TEST_DIRECTORY);
resultData += "/data/bunny1000AlphaWrapping20Wkt.txt";
std::ifstream bunnyFSResult(resultData.c_str());
BOOST_REQUIRE(bunnyFSResult.good());
std::ostringstream resultWkt;
resultWkt << bunnyFSResult.rdbuf();

std::unique_ptr<Geometry> alphaWrappingExpectedGeom(io::readWkt(resultWkt.str()));
BOOST_REQUIRE(alphaWrappingExpectedGeom->is3D());

BOOST_CHECK(sfcgal_geometry_covers_3d(geomAlphaWrapping, alphaWrappingExpectedGeom.get()));
sfcgal_geometry_delete(geomAlphaWrapping);
}
#endif

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit 3a32380

Please sign in to comment.