2020-09-14 14:27:55 +00:00
|
|
|
#include "Exception.hpp"
|
2013-06-24 17:35:49 +00:00
|
|
|
#include "TriangleMesh.hpp"
|
2021-05-17 18:25:59 +00:00
|
|
|
#include "TriangleMeshSlicer.hpp"
|
2021-06-01 13:49:19 +00:00
|
|
|
#include "MeshSplitImpl.hpp"
|
2013-11-23 18:41:40 +00:00
|
|
|
#include "ClipperUtils.hpp"
|
2013-11-24 00:15:52 +00:00
|
|
|
#include "Geometry.hpp"
|
New BuildVolume class was created, which detects build volume type (rectangular,
circular, convex, concave) and performs efficient collision detection agains these build
volumes. As of now, collision detection is performed against a convex
hull of a concave build volume for efficency.
GCodeProcessor::Result renamed out of GCodeProcessor to GCodeProcessorResult,
so it could be forward declared.
Plater newly exports BuildVolume, not Bed3D. Bed3D is a rendering class,
while BuildVolume is a purely geometric class.
Reduced usage of global wxGetApp, the Bed3D is passed as a parameter
to View3D/Preview/GLCanvas.
Convex hull code was extracted from Geometry.cpp/hpp to Geometry/ConvexHulll.cpp,hpp.
New test inside_convex_polygon().
New efficent point inside polygon test: Decompose convex hull
to bottom / top parts and use the decomposition to detect point inside
a convex polygon in O(log n). decompose_convex_polygon_top_bottom(),
inside_convex_polygon().
New Circle constructing functions: circle_ransac() and circle_taubin_newton().
New polygon_is_convex() test with unit tests.
2021-11-16 09:15:51 +00:00
|
|
|
#include "Geometry/ConvexHull.hpp"
|
2021-06-01 13:49:19 +00:00
|
|
|
#include "Point.hpp"
|
2021-06-04 12:11:39 +00:00
|
|
|
#include "Execution/ExecutionTBB.hpp"
|
|
|
|
#include "Execution/ExecutionSeq.hpp"
|
2021-09-20 15:12:22 +00:00
|
|
|
#include "Utils.hpp"
|
2021-05-17 18:25:59 +00:00
|
|
|
|
2019-05-21 20:33:23 +00:00
|
|
|
#include <libqhullcpp/Qhull.h>
|
|
|
|
#include <libqhullcpp/QhullFacetList.h>
|
|
|
|
#include <libqhullcpp/QhullVertexSet.h>
|
2021-05-17 18:25:59 +00:00
|
|
|
|
2014-01-07 14:40:38 +00:00
|
|
|
#include <cmath>
|
2013-09-09 19:41:28 +00:00
|
|
|
#include <deque>
|
2016-11-26 15:07:36 +00:00
|
|
|
#include <queue>
|
2013-09-07 19:08:53 +00:00
|
|
|
#include <vector>
|
|
|
|
#include <utility>
|
2013-09-07 12:06:09 +00:00
|
|
|
#include <algorithm>
|
2018-08-09 14:35:28 +00:00
|
|
|
#include <type_traits>
|
2017-02-26 21:17:39 +00:00
|
|
|
|
2017-03-03 11:53:05 +00:00
|
|
|
#include <boost/log/trivial.hpp>
|
2021-09-21 08:45:57 +00:00
|
|
|
#include <boost/nowide/cstdio.hpp>
|
2021-09-24 08:15:46 +00:00
|
|
|
#include <boost/predef/other/endian.h>
|
2017-03-03 11:53:05 +00:00
|
|
|
|
2019-01-14 10:06:52 +00:00
|
|
|
#include <Eigen/Core>
|
2018-08-15 10:50:06 +00:00
|
|
|
#include <Eigen/Dense>
|
|
|
|
|
2013-09-07 19:08:53 +00:00
|
|
|
#include <assert.h>
|
2013-06-24 17:35:49 +00:00
|
|
|
|
2013-07-07 20:36:14 +00:00
|
|
|
namespace Slic3r {
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
static void update_bounding_box(const indexed_triangle_set &its, TriangleMeshStats &out)
|
2016-11-27 22:06:45 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
BoundingBoxf3 bbox = Slic3r::bounding_box(its);
|
|
|
|
out.min = bbox.min.cast<float>();
|
|
|
|
out.max = bbox.max.cast<float>();
|
|
|
|
out.size = out.max - out.min;
|
|
|
|
}
|
2016-11-27 22:06:45 +00:00
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
static void fill_initial_stats(const indexed_triangle_set &its, TriangleMeshStats &out)
|
|
|
|
{
|
|
|
|
out.number_of_facets = its.indices.size();
|
|
|
|
out.volume = its_volume(its);
|
|
|
|
update_bounding_box(its, out);
|
2016-11-27 22:06:45 +00:00
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
const std::vector<Vec3i> face_neighbors = its_face_neighbors(its);
|
|
|
|
out.number_of_parts = its_number_of_patches(its, face_neighbors);
|
|
|
|
out.open_edges = its_num_open_edges(face_neighbors);
|
|
|
|
}
|
2016-11-27 22:06:45 +00:00
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
TriangleMesh::TriangleMesh(const std::vector<Vec3f> &vertices, const std::vector<Vec3i> &faces) : its { faces, vertices }
|
|
|
|
{
|
|
|
|
fill_initial_stats(this->its, m_stats);
|
|
|
|
}
|
2018-05-03 09:09:13 +00:00
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
TriangleMesh::TriangleMesh(std::vector<Vec3f> &&vertices, const std::vector<Vec3i> &&faces) : its { std::move(faces), std::move(vertices) }
|
|
|
|
{
|
|
|
|
fill_initial_stats(this->its, m_stats);
|
2016-11-27 22:06:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
TriangleMesh::TriangleMesh(const indexed_triangle_set &its) : its(its)
|
2020-01-23 09:57:51 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
fill_initial_stats(this->its, m_stats);
|
|
|
|
}
|
|
|
|
|
2021-10-06 08:53:42 +00:00
|
|
|
TriangleMesh::TriangleMesh(indexed_triangle_set &&its, const RepairedMeshErrors& errors/* = RepairedMeshErrors()*/) : its(std::move(its))
|
2021-09-20 15:12:22 +00:00
|
|
|
{
|
2021-10-06 15:24:22 +00:00
|
|
|
m_stats.repaired_errors = errors;
|
2021-09-20 15:12:22 +00:00
|
|
|
fill_initial_stats(this->its, m_stats);
|
2020-01-23 09:57:51 +00:00
|
|
|
}
|
|
|
|
|
2019-01-23 13:00:03 +00:00
|
|
|
// #define SLIC3R_TRACE_REPAIR
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
static void trianglemesh_repair_on_import(stl_file &stl)
|
2013-09-11 07:49:28 +00:00
|
|
|
{
|
2014-01-16 10:25:26 +00:00
|
|
|
// admesh fails when repairing empty meshes
|
2021-09-20 15:12:22 +00:00
|
|
|
if (stl.stats.number_of_facets == 0)
|
2021-05-17 18:25:59 +00:00
|
|
|
return;
|
2017-03-03 12:31:51 +00:00
|
|
|
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started";
|
2019-06-04 16:25:53 +00:00
|
|
|
|
2013-06-24 17:35:49 +00:00
|
|
|
// checking exact
|
2019-01-23 13:00:03 +00:00
|
|
|
#ifdef SLIC3R_TRACE_REPAIR
|
2021-05-17 18:25:59 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact";
|
2019-01-23 13:00:03 +00:00
|
|
|
#endif /* SLIC3R_TRACE_REPAIR */
|
2021-09-20 15:12:22 +00:00
|
|
|
assert(stl_validate(&stl));
|
2021-05-17 18:25:59 +00:00
|
|
|
stl_check_facets_exact(&stl);
|
2021-09-20 15:12:22 +00:00
|
|
|
assert(stl_validate(&stl));
|
2013-06-24 17:35:49 +00:00
|
|
|
stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge);
|
|
|
|
stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge);
|
|
|
|
stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge);
|
|
|
|
|
|
|
|
// checking nearby
|
2015-08-23 21:35:11 +00:00
|
|
|
//int last_edges_fixed = 0;
|
2021-05-17 18:25:59 +00:00
|
|
|
float tolerance = (float)stl.stats.shortest_edge;
|
|
|
|
float increment = (float)stl.stats.bounding_diameter / 10000.0f;
|
2013-06-24 17:35:49 +00:00
|
|
|
int iterations = 2;
|
2021-09-20 15:12:22 +00:00
|
|
|
if (stl.stats.connected_facets_3_edge < int(stl.stats.number_of_facets)) {
|
|
|
|
// Not a manifold, some triangles have unconnected edges.
|
|
|
|
for (int i = 0; i < iterations; ++ i) {
|
|
|
|
if (stl.stats.connected_facets_3_edge < int(stl.stats.number_of_facets)) {
|
|
|
|
// Still not a manifold, some triangles have unconnected edges.
|
2013-06-24 18:36:51 +00:00
|
|
|
//printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations);
|
2019-01-23 13:00:03 +00:00
|
|
|
#ifdef SLIC3R_TRACE_REPAIR
|
2021-05-17 18:25:59 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby";
|
2019-01-23 13:00:03 +00:00
|
|
|
#endif /* SLIC3R_TRACE_REPAIR */
|
2021-05-17 18:25:59 +00:00
|
|
|
stl_check_facets_nearby(&stl, tolerance);
|
2013-06-24 18:36:51 +00:00
|
|
|
//printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed);
|
2015-08-23 21:35:11 +00:00
|
|
|
//last_edges_fixed = stl.stats.edges_fixed;
|
2013-06-24 17:35:49 +00:00
|
|
|
tolerance += increment;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-20 15:12:22 +00:00
|
|
|
assert(stl_validate(&stl));
|
2013-06-24 17:35:49 +00:00
|
|
|
|
|
|
|
// remove_unconnected
|
2019-03-28 11:37:10 +00:00
|
|
|
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) {
|
2019-01-23 13:00:03 +00:00
|
|
|
#ifdef SLIC3R_TRACE_REPAIR
|
2018-09-25 12:30:57 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets";
|
2019-01-23 13:00:03 +00:00
|
|
|
#endif /* SLIC3R_TRACE_REPAIR */
|
2013-06-24 17:35:49 +00:00
|
|
|
stl_remove_unconnected_facets(&stl);
|
2021-09-20 15:12:22 +00:00
|
|
|
assert(stl_validate(&stl));
|
2013-06-24 18:36:51 +00:00
|
|
|
}
|
2013-06-24 17:35:49 +00:00
|
|
|
|
|
|
|
// fill_holes
|
2019-01-14 10:06:52 +00:00
|
|
|
#if 0
|
|
|
|
// Don't fill holes, the current algorithm does more harm than good on complex holes.
|
|
|
|
// Rather let the slicing algorithm close gaps in 2D slices.
|
2013-06-24 17:35:49 +00:00
|
|
|
if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
|
2019-01-23 13:00:03 +00:00
|
|
|
#ifdef SLIC3R_TRACE_REPAIR
|
2018-09-25 12:30:57 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "\tstl_fill_holes";
|
2019-01-23 13:00:03 +00:00
|
|
|
#endif /* SLIC3R_TRACE_REPAIR */
|
2013-06-24 17:35:49 +00:00
|
|
|
stl_fill_holes(&stl);
|
2014-09-23 12:34:37 +00:00
|
|
|
stl_clear_error(&stl);
|
2013-06-24 18:36:51 +00:00
|
|
|
}
|
2019-01-14 10:06:52 +00:00
|
|
|
#endif
|
2018-01-18 08:15:04 +00:00
|
|
|
|
2018-02-15 15:10:47 +00:00
|
|
|
// normal_directions
|
2019-01-23 13:00:03 +00:00
|
|
|
#ifdef SLIC3R_TRACE_REPAIR
|
2018-09-25 12:30:57 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions";
|
2019-01-23 13:00:03 +00:00
|
|
|
#endif /* SLIC3R_TRACE_REPAIR */
|
2018-02-15 15:10:47 +00:00
|
|
|
stl_fix_normal_directions(&stl);
|
2021-09-20 15:12:22 +00:00
|
|
|
assert(stl_validate(&stl));
|
2018-01-18 08:15:04 +00:00
|
|
|
|
2013-06-24 17:35:49 +00:00
|
|
|
// normal_values
|
2019-01-23 13:00:03 +00:00
|
|
|
#ifdef SLIC3R_TRACE_REPAIR
|
2018-09-25 12:30:57 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values";
|
2019-01-23 13:00:03 +00:00
|
|
|
#endif /* SLIC3R_TRACE_REPAIR */
|
2013-06-24 17:35:49 +00:00
|
|
|
stl_fix_normal_values(&stl);
|
2021-09-20 15:12:22 +00:00
|
|
|
assert(stl_validate(&stl));
|
2013-07-13 17:00:38 +00:00
|
|
|
|
|
|
|
// always calculate the volume and reverse all normals if volume is negative
|
2019-01-23 13:00:03 +00:00
|
|
|
#ifdef SLIC3R_TRACE_REPAIR
|
2018-09-25 12:30:57 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume";
|
2019-01-23 13:00:03 +00:00
|
|
|
#endif /* SLIC3R_TRACE_REPAIR */
|
2021-09-20 15:12:22 +00:00
|
|
|
// If the volume is negative, all the facets are flipped and added to stats.facets_reversed.
|
2013-07-13 17:00:38 +00:00
|
|
|
stl_calculate_volume(&stl);
|
2021-09-20 15:12:22 +00:00
|
|
|
assert(stl_validate(&stl));
|
2013-07-13 17:00:38 +00:00
|
|
|
|
|
|
|
// neighbors
|
2019-01-23 13:00:03 +00:00
|
|
|
#ifdef SLIC3R_TRACE_REPAIR
|
2018-09-25 12:30:57 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors";
|
2019-01-23 13:00:03 +00:00
|
|
|
#endif /* SLIC3R_TRACE_REPAIR */
|
2013-07-13 17:00:38 +00:00
|
|
|
stl_verify_neighbors(&stl);
|
2021-09-20 15:12:22 +00:00
|
|
|
assert(stl_validate(&stl));
|
2017-03-03 12:31:51 +00:00
|
|
|
|
2021-09-14 09:58:07 +00:00
|
|
|
//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
|
2021-09-20 15:12:22 +00:00
|
|
|
if (auto nr_degenerated = stl.stats.degenerate_facets; stl.stats.number_of_facets > 0 && nr_degenerated > 0)
|
|
|
|
stl_check_facets_exact(&stl);
|
2021-09-14 09:58:07 +00:00
|
|
|
|
2017-03-03 12:31:51 +00:00
|
|
|
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
|
2013-06-24 17:35:49 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
bool TriangleMesh::ReadSTLFile(const char* input_file, bool repair)
|
|
|
|
{
|
|
|
|
stl_file stl;
|
|
|
|
if (! stl_open(&stl, input_file))
|
|
|
|
return false;
|
|
|
|
if (repair)
|
|
|
|
trianglemesh_repair_on_import(stl);
|
|
|
|
|
|
|
|
m_stats.number_of_facets = stl.stats.number_of_facets;
|
|
|
|
m_stats.min = stl.stats.min;
|
|
|
|
m_stats.max = stl.stats.max;
|
|
|
|
m_stats.size = stl.stats.size;
|
|
|
|
m_stats.volume = stl.stats.volume;
|
|
|
|
|
|
|
|
auto facets_w_1_bad_edge = stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge;
|
|
|
|
auto facets_w_2_bad_edge = stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge;
|
|
|
|
auto facets_w_3_bad_edge = stl.stats.number_of_facets - stl.stats.connected_facets_1_edge;
|
2021-09-21 14:03:30 +00:00
|
|
|
m_stats.open_edges = stl.stats.backwards_edges + facets_w_1_bad_edge + facets_w_2_bad_edge * 2 + facets_w_3_bad_edge * 3;
|
2021-09-20 15:12:22 +00:00
|
|
|
|
2021-10-06 08:53:42 +00:00
|
|
|
m_stats.repaired_errors = { stl.stats.edges_fixed,
|
|
|
|
stl.stats.degenerate_facets,
|
|
|
|
stl.stats.facets_removed,
|
|
|
|
stl.stats.facets_reversed,
|
|
|
|
stl.stats.backwards_edges };
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
m_stats.number_of_parts = stl.stats.number_of_parts;
|
|
|
|
|
|
|
|
stl_generate_shared_vertices(&stl, this->its);
|
|
|
|
return true;
|
2017-08-02 14:05:18 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
bool TriangleMesh::write_ascii(const char* output_file)
|
|
|
|
{
|
|
|
|
return its_write_stl_ascii(output_file, "", this->its);
|
2017-08-02 14:05:18 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
bool TriangleMesh::write_binary(const char* output_file)
|
|
|
|
{
|
|
|
|
return its_write_stl_binary(output_file, "", this->its);
|
2014-04-25 15:14:39 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
float TriangleMesh::volume()
|
2014-08-08 19:48:59 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
if (m_stats.volume == -1)
|
|
|
|
m_stats.volume = its_volume(this->its);
|
|
|
|
return m_stats.volume;
|
2014-08-08 19:48:59 +00:00
|
|
|
}
|
|
|
|
|
2019-10-03 12:23:03 +00:00
|
|
|
void TriangleMesh::WriteOBJFile(const char* output_file) const
|
2014-09-21 08:51:36 +00:00
|
|
|
{
|
2019-06-11 15:08:47 +00:00
|
|
|
its_write_obj(this->its, output_file);
|
2013-06-24 17:35:49 +00:00
|
|
|
}
|
2013-06-24 18:11:56 +00:00
|
|
|
|
2013-08-04 19:34:26 +00:00
|
|
|
void TriangleMesh::scale(float factor)
|
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
this->scale(Vec3f(factor, factor, factor));
|
2013-08-04 19:34:26 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
void TriangleMesh::scale(const Vec3f &versor)
|
2013-09-09 21:38:49 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
// Scale extents.
|
|
|
|
auto s = versor.array();
|
|
|
|
m_stats.min.array() *= s;
|
|
|
|
m_stats.max.array() *= s;
|
|
|
|
// Scale size.
|
|
|
|
m_stats.size.array() *= s;
|
|
|
|
// Scale volume.
|
|
|
|
if (m_stats.volume > 0.0)
|
|
|
|
m_stats.volume *= s(0) * s(1) * s(2);
|
|
|
|
if (versor.x() == versor.y() && versor.x() == versor.z()) {
|
|
|
|
float s = versor.x();
|
|
|
|
for (stl_vertex &v : this->its.vertices)
|
|
|
|
v *= s;
|
|
|
|
} else {
|
|
|
|
for (stl_vertex &v : this->its.vertices) {
|
|
|
|
v.x() *= versor.x();
|
|
|
|
v.y() *= versor.y();
|
|
|
|
v.z() *= versor.z();
|
|
|
|
}
|
2021-05-17 18:25:59 +00:00
|
|
|
}
|
2013-09-09 21:38:49 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
void TriangleMesh::translate(const Vec3f &displacement)
|
2013-08-05 08:48:38 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
if (displacement.x() != 0.f || displacement.y() != 0.f || displacement.z() != 0.f) {
|
|
|
|
for (stl_vertex& v : this->its.vertices)
|
|
|
|
v += displacement;
|
|
|
|
m_stats.min += displacement;
|
|
|
|
m_stats.max += displacement;
|
|
|
|
}
|
2013-08-05 08:48:38 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
void TriangleMesh::translate(float x, float y, float z)
|
2019-04-05 08:08:34 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
this->translate(Vec3f(x, y, z));
|
2019-04-05 08:08:34 +00:00
|
|
|
}
|
|
|
|
|
2015-04-16 19:22:04 +00:00
|
|
|
void TriangleMesh::rotate(float angle, const Axis &axis)
|
2014-04-25 15:50:03 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
if (angle != 0.f) {
|
|
|
|
angle = Slic3r::Geometry::rad2deg(angle);
|
|
|
|
switch (axis) {
|
|
|
|
case X: its_rotate_x(this->its, angle); break;
|
|
|
|
case Y: its_rotate_y(this->its, angle); break;
|
|
|
|
case Z: its_rotate_z(this->its, angle); break;
|
|
|
|
default: assert(false); return;
|
|
|
|
}
|
|
|
|
update_bounding_box(this->its, this->m_stats);
|
2015-04-16 19:22:04 +00:00
|
|
|
}
|
2014-04-25 15:50:03 +00:00
|
|
|
}
|
|
|
|
|
2018-08-27 12:00:53 +00:00
|
|
|
void TriangleMesh::rotate(float angle, const Vec3d& axis)
|
2015-04-16 19:22:04 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
if (angle != 0.f) {
|
|
|
|
Vec3d axis_norm = axis.normalized();
|
|
|
|
Transform3d m = Transform3d::Identity();
|
|
|
|
m.rotate(Eigen::AngleAxisd(angle, axis_norm));
|
|
|
|
its_transform(its, m);
|
|
|
|
update_bounding_box(this->its, this->m_stats);
|
|
|
|
}
|
2015-04-16 19:22:04 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
void TriangleMesh::mirror(const Axis axis)
|
2015-04-16 19:22:04 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
switch (axis) {
|
|
|
|
case X:
|
|
|
|
for (stl_vertex &v : its.vertices)
|
|
|
|
v.x() *= -1.f;
|
|
|
|
break;
|
|
|
|
case Y:
|
|
|
|
for (stl_vertex& v : this->its.vertices)
|
|
|
|
v.y() *= -1.0;
|
|
|
|
break;
|
|
|
|
case Z:
|
2019-06-13 14:33:50 +00:00
|
|
|
for (stl_vertex &v : this->its.vertices)
|
2021-09-20 15:12:22 +00:00
|
|
|
v.z() *= -1.0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(false);
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
its_flip_triangles(this->its);
|
|
|
|
int iaxis = int(axis);
|
|
|
|
std::swap(m_stats.min[iaxis], m_stats.max[iaxis]);
|
|
|
|
m_stats.min[iaxis] *= -1.0;
|
|
|
|
m_stats.max[iaxis] *= -1.0;
|
2014-04-25 15:50:03 +00:00
|
|
|
}
|
|
|
|
|
2019-04-03 09:12:03 +00:00
|
|
|
void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed)
|
2014-06-14 19:14:33 +00:00
|
|
|
{
|
2019-06-11 15:08:47 +00:00
|
|
|
its_transform(its, t);
|
2021-10-01 12:28:36 +00:00
|
|
|
double det = t.matrix().block(0, 0, 3, 3).determinant();
|
|
|
|
if (fix_left_handed && det < 0.) {
|
2021-09-20 15:12:22 +00:00
|
|
|
its_flip_triangles(its);
|
2021-10-01 12:28:36 +00:00
|
|
|
det = -det;
|
|
|
|
}
|
|
|
|
m_stats.volume *= det;
|
2021-09-20 15:12:22 +00:00
|
|
|
update_bounding_box(this->its, this->m_stats);
|
2018-01-30 08:27:10 +00:00
|
|
|
}
|
|
|
|
|
2019-04-26 15:28:31 +00:00
|
|
|
void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed)
|
|
|
|
{
|
2019-06-11 15:08:47 +00:00
|
|
|
its_transform(its, m);
|
2021-10-01 12:28:36 +00:00
|
|
|
double det = m.block(0, 0, 3, 3).determinant();
|
|
|
|
if (fix_left_handed && det < 0.) {
|
2021-09-20 15:12:22 +00:00
|
|
|
its_flip_triangles(its);
|
2021-10-01 12:28:36 +00:00
|
|
|
det = -det;
|
|
|
|
}
|
|
|
|
m_stats.volume *= det;
|
2021-09-20 15:12:22 +00:00
|
|
|
update_bounding_box(this->its, this->m_stats);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TriangleMesh::flip_triangles()
|
|
|
|
{
|
|
|
|
its_flip_triangles(its);
|
|
|
|
m_stats.volume = - m_stats.volume;
|
2019-04-26 15:28:31 +00:00
|
|
|
}
|
|
|
|
|
2013-08-05 17:22:33 +00:00
|
|
|
void TriangleMesh::align_to_origin()
|
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
this->translate(- m_stats.min(0), - m_stats.min(1), - m_stats.min(2));
|
2013-08-05 17:22:33 +00:00
|
|
|
}
|
|
|
|
|
2013-08-05 17:52:37 +00:00
|
|
|
void TriangleMesh::rotate(double angle, Point* center)
|
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
if (angle != 0.) {
|
|
|
|
Vec2f c = center->cast<float>();
|
|
|
|
this->translate(-c(0), -c(1), 0);
|
|
|
|
its_rotate_z(this->its, (float)angle);
|
|
|
|
this->translate(c(0), c(1), 0);
|
|
|
|
}
|
2017-02-26 21:17:39 +00:00
|
|
|
}
|
|
|
|
|
2019-04-01 10:27:45 +00:00
|
|
|
/**
|
|
|
|
* Calculates whether or not the mesh is splittable.
|
|
|
|
*/
|
|
|
|
bool TriangleMesh::is_splittable() const
|
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
return its_is_splittable(this->its);
|
2013-08-05 17:52:37 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
std::vector<TriangleMesh> TriangleMesh::split() const
|
2014-01-15 19:31:38 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
std::vector<indexed_triangle_set> itss = its_split(this->its);
|
|
|
|
std::vector<TriangleMesh> out;
|
|
|
|
out.reserve(itss.size());
|
|
|
|
for (indexed_triangle_set &m : itss) {
|
|
|
|
// The TriangleMesh constructor shall fill in the mesh statistics including volume.
|
|
|
|
out.emplace_back(std::move(m));
|
|
|
|
if (TriangleMesh &triangle_mesh = out.back(); triangle_mesh.volume() < 0)
|
|
|
|
// Some source mesh parts may be incorrectly oriented. Correct them.
|
|
|
|
triangle_mesh.flip_triangles();
|
2021-06-04 12:39:35 +00:00
|
|
|
|
2014-01-15 19:31:38 +00:00
|
|
|
}
|
2021-09-20 15:12:22 +00:00
|
|
|
return out;
|
2014-01-15 19:31:38 +00:00
|
|
|
}
|
|
|
|
|
2018-08-22 13:34:03 +00:00
|
|
|
void TriangleMesh::merge(const TriangleMesh &mesh)
|
2014-01-15 19:31:38 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
its_merge(this->its, mesh.its);
|
|
|
|
m_stats = m_stats.merge(mesh.m_stats);
|
2014-01-15 19:31:38 +00:00
|
|
|
}
|
|
|
|
|
2017-06-12 12:25:35 +00:00
|
|
|
// Calculate projection of the mesh into the XY plane, in scaled coordinates.
|
|
|
|
//FIXME This could be extremely slow! Use it for tiny meshes only!
|
|
|
|
ExPolygons TriangleMesh::horizontal_projection() const
|
2014-01-15 19:31:38 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
return union_ex(project_mesh(this->its, Transform3d::Identity(), []() {}));
|
2014-01-15 19:31:38 +00:00
|
|
|
}
|
|
|
|
|
2019-01-14 10:06:52 +00:00
|
|
|
// 2D convex hull of a 3D mesh projected into the Z=0 plane.
|
2017-06-12 12:25:35 +00:00
|
|
|
Polygon TriangleMesh::convex_hull()
|
2014-01-15 19:31:38 +00:00
|
|
|
{
|
|
|
|
Points pp;
|
2019-06-10 16:30:54 +00:00
|
|
|
pp.reserve(this->its.vertices.size());
|
|
|
|
for (size_t i = 0; i < this->its.vertices.size(); ++ i) {
|
|
|
|
const stl_vertex &v = this->its.vertices[i];
|
2018-08-22 13:03:35 +00:00
|
|
|
pp.emplace_back(Point::new_scale(v(0), v(1)));
|
2014-01-15 19:31:38 +00:00
|
|
|
}
|
2015-01-19 17:53:04 +00:00
|
|
|
return Slic3r::Geometry::convex_hull(pp);
|
2014-01-15 19:31:38 +00:00
|
|
|
}
|
|
|
|
|
2018-08-22 13:34:03 +00:00
|
|
|
BoundingBoxf3 TriangleMesh::bounding_box() const
|
2014-09-21 08:51:36 +00:00
|
|
|
{
|
|
|
|
BoundingBoxf3 bb;
|
2017-09-12 14:48:44 +00:00
|
|
|
bb.defined = true;
|
2021-09-20 15:12:22 +00:00
|
|
|
bb.min = m_stats.min.cast<double>();
|
|
|
|
bb.max = m_stats.max.cast<double>();
|
2014-09-21 08:51:36 +00:00
|
|
|
return bb;
|
|
|
|
}
|
|
|
|
|
2019-01-26 17:51:34 +00:00
|
|
|
BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const
|
2018-08-15 10:50:06 +00:00
|
|
|
{
|
2019-01-26 17:51:34 +00:00
|
|
|
BoundingBoxf3 bbox;
|
2021-09-20 15:12:22 +00:00
|
|
|
for (const stl_vertex &v : this->its.vertices)
|
|
|
|
bbox.merge(trafo * v.cast<double>());
|
2019-01-26 17:51:34 +00:00
|
|
|
return bbox;
|
2018-08-15 10:50:06 +00:00
|
|
|
}
|
|
|
|
|
New BuildVolume class was created, which detects build volume type (rectangular,
circular, convex, concave) and performs efficient collision detection agains these build
volumes. As of now, collision detection is performed against a convex
hull of a concave build volume for efficency.
GCodeProcessor::Result renamed out of GCodeProcessor to GCodeProcessorResult,
so it could be forward declared.
Plater newly exports BuildVolume, not Bed3D. Bed3D is a rendering class,
while BuildVolume is a purely geometric class.
Reduced usage of global wxGetApp, the Bed3D is passed as a parameter
to View3D/Preview/GLCanvas.
Convex hull code was extracted from Geometry.cpp/hpp to Geometry/ConvexHulll.cpp,hpp.
New test inside_convex_polygon().
New efficent point inside polygon test: Decompose convex hull
to bottom / top parts and use the decomposition to detect point inside
a convex polygon in O(log n). decompose_convex_polygon_top_bottom(),
inside_convex_polygon().
New Circle constructing functions: circle_ransac() and circle_taubin_newton().
New polygon_is_convex() test with unit tests.
2021-11-16 09:15:51 +00:00
|
|
|
BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& trafod, double world_min_z) const
|
2021-09-16 11:38:02 +00:00
|
|
|
{
|
New BuildVolume class was created, which detects build volume type (rectangular,
circular, convex, concave) and performs efficient collision detection agains these build
volumes. As of now, collision detection is performed against a convex
hull of a concave build volume for efficency.
GCodeProcessor::Result renamed out of GCodeProcessor to GCodeProcessorResult,
so it could be forward declared.
Plater newly exports BuildVolume, not Bed3D. Bed3D is a rendering class,
while BuildVolume is a purely geometric class.
Reduced usage of global wxGetApp, the Bed3D is passed as a parameter
to View3D/Preview/GLCanvas.
Convex hull code was extracted from Geometry.cpp/hpp to Geometry/ConvexHulll.cpp,hpp.
New test inside_convex_polygon().
New efficent point inside polygon test: Decompose convex hull
to bottom / top parts and use the decomposition to detect point inside
a convex polygon in O(log n). decompose_convex_polygon_top_bottom(),
inside_convex_polygon().
New Circle constructing functions: circle_ransac() and circle_taubin_newton().
New polygon_is_convex() test with unit tests.
2021-11-16 09:15:51 +00:00
|
|
|
// 1) Allocate transformed vertices with their position with respect to print bed surface.
|
|
|
|
std::vector<char> sides;
|
|
|
|
size_t num_above = 0;
|
|
|
|
Eigen::AlignedBox<float, 3> bbox;
|
|
|
|
Transform3f trafo = trafod.cast<float>();
|
|
|
|
sides.reserve(its.vertices.size());
|
|
|
|
for (const stl_vertex &v : this->its.vertices) {
|
|
|
|
const stl_vertex pt = trafo * v;
|
|
|
|
const int sign = pt.z() > world_min_z ? 1 : pt.z() < world_min_z ? -1 : 0;
|
|
|
|
sides.emplace_back(sign);
|
|
|
|
if (sign >= 0) {
|
|
|
|
// Vertex above or on print bed surface. Test whether it is inside the build volume.
|
|
|
|
++ num_above;
|
|
|
|
bbox.extend(pt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2) Calculate intersections of triangle edges with the build surface.
|
|
|
|
if (num_above < its.vertices.size()) {
|
|
|
|
// Not completely above the build surface and status may still change by testing edges intersecting the build platform.
|
|
|
|
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
|
|
|
const int s[3] = { sides[tri(0)], sides[tri(1)], sides[tri(2)] };
|
|
|
|
if (std::min(s[0], std::min(s[1], s[2])) < 0 && std::max(s[0], std::max(s[1], s[2])) > 0) {
|
|
|
|
// Some edge of this triangle intersects the build platform. Calculate the intersection.
|
|
|
|
int iprev = 2;
|
|
|
|
for (int iedge = 0; iedge < 3; ++ iedge) {
|
|
|
|
if (s[iprev] * s[iedge] == -1) {
|
|
|
|
// edge intersects the build surface. Calculate intersection point.
|
|
|
|
const stl_vertex p1 = trafo * its.vertices[tri(iprev)];
|
|
|
|
const stl_vertex p2 = trafo * its.vertices[tri(iedge)];
|
|
|
|
// Edge crosses the z plane. Calculate intersection point with the plane.
|
|
|
|
const float t = (world_min_z - p1.z()) / (p2.z() - p1.z());
|
|
|
|
bbox.extend(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z));
|
|
|
|
}
|
|
|
|
iprev = iedge;
|
|
|
|
}
|
2021-09-17 07:45:50 +00:00
|
|
|
}
|
|
|
|
}
|
2021-09-16 11:38:02 +00:00
|
|
|
}
|
New BuildVolume class was created, which detects build volume type (rectangular,
circular, convex, concave) and performs efficient collision detection agains these build
volumes. As of now, collision detection is performed against a convex
hull of a concave build volume for efficency.
GCodeProcessor::Result renamed out of GCodeProcessor to GCodeProcessorResult,
so it could be forward declared.
Plater newly exports BuildVolume, not Bed3D. Bed3D is a rendering class,
while BuildVolume is a purely geometric class.
Reduced usage of global wxGetApp, the Bed3D is passed as a parameter
to View3D/Preview/GLCanvas.
Convex hull code was extracted from Geometry.cpp/hpp to Geometry/ConvexHulll.cpp,hpp.
New test inside_convex_polygon().
New efficent point inside polygon test: Decompose convex hull
to bottom / top parts and use the decomposition to detect point inside
a convex polygon in O(log n). decompose_convex_polygon_top_bottom(),
inside_convex_polygon().
New Circle constructing functions: circle_ransac() and circle_taubin_newton().
New polygon_is_convex() test with unit tests.
2021-11-16 09:15:51 +00:00
|
|
|
|
|
|
|
BoundingBoxf3 out;
|
|
|
|
if (! bbox.isEmpty()) {
|
|
|
|
out.min = bbox.min().cast<double>();
|
|
|
|
out.max = bbox.max().cast<double>();
|
|
|
|
out.defined = true;
|
|
|
|
};
|
|
|
|
return out;
|
2021-09-16 11:38:02 +00:00
|
|
|
}
|
|
|
|
|
2018-08-15 10:50:06 +00:00
|
|
|
TriangleMesh TriangleMesh::convex_hull_3d() const
|
|
|
|
{
|
2021-10-22 12:02:39 +00:00
|
|
|
TriangleMesh mesh(its_convex_hull(this->its));
|
|
|
|
// Quite often qhull produces non-manifold mesh.
|
|
|
|
// assert(mesh.stats().manifold());
|
2021-09-21 14:03:30 +00:00
|
|
|
return mesh;
|
2018-08-15 10:50:06 +00:00
|
|
|
}
|
|
|
|
|
2021-05-20 07:09:19 +00:00
|
|
|
std::vector<ExPolygons> TriangleMesh::slice(const std::vector<double> &z) const
|
2019-10-15 11:49:28 +00:00
|
|
|
{
|
|
|
|
// convert doubles to floats
|
|
|
|
std::vector<float> z_f(z.begin(), z.end());
|
2021-05-18 13:05:23 +00:00
|
|
|
return slice_mesh_ex(this->its, z_f, 0.0004f);
|
2019-10-15 11:49:28 +00:00
|
|
|
}
|
|
|
|
|
2019-07-17 13:48:53 +00:00
|
|
|
size_t TriangleMesh::memsize() const
|
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
size_t memsize = 8 + this->its.memsize() + sizeof(this->m_stats);
|
2021-05-17 18:25:59 +00:00
|
|
|
return memsize;
|
2019-07-17 13:48:53 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 13:49:19 +00:00
|
|
|
// Create a mapping from triangle edge into face.
|
|
|
|
struct EdgeToFace {
|
|
|
|
// Index of the 1st vertex of the triangle edge. vertex_low <= vertex_high.
|
|
|
|
int vertex_low;
|
|
|
|
// Index of the 2nd vertex of the triangle edge.
|
|
|
|
int vertex_high;
|
|
|
|
// Index of a triangular face.
|
|
|
|
int face;
|
|
|
|
// Index of edge in the face, starting with 1. Negative indices if the edge was stored reverse in (vertex_low, vertex_high).
|
|
|
|
int face_edge;
|
|
|
|
bool operator==(const EdgeToFace &other) const { return vertex_low == other.vertex_low && vertex_high == other.vertex_high; }
|
|
|
|
bool operator<(const EdgeToFace &other) const { return vertex_low < other.vertex_low || (vertex_low == other.vertex_low && vertex_high < other.vertex_high); }
|
|
|
|
};
|
2021-06-01 17:30:26 +00:00
|
|
|
|
2021-07-26 15:02:56 +00:00
|
|
|
template<typename FaceFilter, typename ThrowOnCancelCallback>
|
2021-06-01 13:49:19 +00:00
|
|
|
static std::vector<EdgeToFace> create_edge_map(
|
2021-07-26 15:02:56 +00:00
|
|
|
const indexed_triangle_set &its, FaceFilter face_filter, ThrowOnCancelCallback throw_on_cancel)
|
2021-05-17 18:25:59 +00:00
|
|
|
{
|
2017-03-03 16:36:07 +00:00
|
|
|
std::vector<EdgeToFace> edges_map;
|
2021-07-26 15:02:56 +00:00
|
|
|
edges_map.reserve(its.indices.size() * 3);
|
2021-05-17 18:25:59 +00:00
|
|
|
for (uint32_t facet_idx = 0; facet_idx < its.indices.size(); ++ facet_idx)
|
2021-07-26 15:02:56 +00:00
|
|
|
if (face_filter(facet_idx))
|
|
|
|
for (int i = 0; i < 3; ++ i) {
|
|
|
|
edges_map.push_back({});
|
|
|
|
EdgeToFace &e2f = edges_map.back();
|
|
|
|
e2f.vertex_low = its.indices[facet_idx][i];
|
|
|
|
e2f.vertex_high = its.indices[facet_idx][(i + 1) % 3];
|
|
|
|
e2f.face = facet_idx;
|
|
|
|
// 1 based indexing, to be always strictly positive.
|
|
|
|
e2f.face_edge = i + 1;
|
|
|
|
if (e2f.vertex_low > e2f.vertex_high) {
|
|
|
|
// Sort the vertices
|
|
|
|
std::swap(e2f.vertex_low, e2f.vertex_high);
|
|
|
|
// and make the face_edge negative to indicate a flipped edge.
|
|
|
|
e2f.face_edge = - e2f.face_edge;
|
|
|
|
}
|
2017-03-03 16:36:07 +00:00
|
|
|
}
|
2018-03-28 15:05:31 +00:00
|
|
|
throw_on_cancel();
|
2017-03-03 16:36:07 +00:00
|
|
|
std::sort(edges_map.begin(), edges_map.end());
|
|
|
|
|
2021-06-01 13:49:19 +00:00
|
|
|
return edges_map;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map from a face edge to a unique edge identifier or -1 if no neighbor exists.
|
|
|
|
// Two neighbor faces share a unique edge identifier even if they are flipped.
|
2021-07-26 15:02:56 +00:00
|
|
|
template<typename FaceFilter, typename ThrowOnCancelCallback>
|
|
|
|
static inline std::vector<Vec3i> its_face_edge_ids_impl(const indexed_triangle_set &its, FaceFilter face_filter, ThrowOnCancelCallback throw_on_cancel)
|
2021-06-01 13:49:19 +00:00
|
|
|
{
|
|
|
|
std::vector<Vec3i> out(its.indices.size(), Vec3i(-1, -1, -1));
|
|
|
|
|
2021-07-26 15:02:56 +00:00
|
|
|
std::vector<EdgeToFace> edges_map = create_edge_map(its, face_filter, throw_on_cancel);
|
2021-06-01 13:49:19 +00:00
|
|
|
|
2017-03-03 16:36:07 +00:00
|
|
|
// Assign a unique common edge id to touching triangle edges.
|
|
|
|
int num_edges = 0;
|
|
|
|
for (size_t i = 0; i < edges_map.size(); ++ i) {
|
|
|
|
EdgeToFace &edge_i = edges_map[i];
|
|
|
|
if (edge_i.face == -1)
|
|
|
|
// This edge has been connected to some neighbor already.
|
|
|
|
continue;
|
|
|
|
// Unconnected edge. Find its neighbor with the correct orientation.
|
|
|
|
size_t j;
|
|
|
|
bool found = false;
|
|
|
|
for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++ j)
|
|
|
|
if (edge_i.face_edge * edges_map[j].face_edge < 0 && edges_map[j].face != -1) {
|
|
|
|
// Faces touching with opposite oriented edges and none of the edges is connected yet.
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (! found) {
|
|
|
|
//FIXME Vojtech: Trying to find an edge with equal orientation. This smells.
|
|
|
|
// admesh can assign the same edge ID to more than two facets (which is
|
|
|
|
// still topologically correct), so we have to search for a duplicate of
|
|
|
|
// this edge too in case it was already seen in this orientation
|
|
|
|
for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++ j)
|
|
|
|
if (edges_map[j].face != -1) {
|
|
|
|
// Faces touching with equally oriented edges and none of the edges is connected yet.
|
|
|
|
found = true;
|
|
|
|
break;
|
2017-02-26 21:17:39 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-03 16:36:07 +00:00
|
|
|
// Assign an edge index to the 1st face.
|
2021-05-18 13:05:23 +00:00
|
|
|
out[edge_i.face](std::abs(edge_i.face_edge) - 1) = num_edges;
|
2017-03-03 16:36:07 +00:00
|
|
|
if (found) {
|
|
|
|
EdgeToFace &edge_j = edges_map[j];
|
2021-05-18 13:05:23 +00:00
|
|
|
out[edge_j.face](std::abs(edge_j.face_edge) - 1) = num_edges;
|
2018-09-17 13:12:13 +00:00
|
|
|
// Mark the edge as connected.
|
|
|
|
edge_j.face = -1;
|
|
|
|
}
|
2017-03-03 16:36:07 +00:00
|
|
|
++ num_edges;
|
2018-03-28 15:05:31 +00:00
|
|
|
if ((i & 0x0ffff) == 0)
|
|
|
|
throw_on_cancel();
|
2017-02-26 21:17:39 +00:00
|
|
|
}
|
2016-11-26 12:45:58 +00:00
|
|
|
|
2018-12-12 09:02:01 +00:00
|
|
|
return out;
|
|
|
|
}
|
2018-03-15 16:14:13 +00:00
|
|
|
|
2021-06-20 13:21:12 +00:00
|
|
|
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its)
|
2018-12-12 09:02:01 +00:00
|
|
|
{
|
2021-07-26 15:02:56 +00:00
|
|
|
return its_face_edge_ids_impl(its, [](const uint32_t){ return true; }, [](){});
|
2018-12-12 09:02:01 +00:00
|
|
|
}
|
|
|
|
|
2021-06-20 13:21:12 +00:00
|
|
|
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::function<void()> throw_on_cancel_callback)
|
2018-12-12 09:02:01 +00:00
|
|
|
{
|
2021-07-26 15:02:56 +00:00
|
|
|
return its_face_edge_ids_impl(its, [](const uint32_t){ return true; }, throw_on_cancel_callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, const std::vector<bool> &face_mask)
|
|
|
|
{
|
|
|
|
return its_face_edge_ids_impl(its, [&face_mask](const uint32_t idx){ return face_mask[idx]; }, [](){});
|
2021-06-20 13:21:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices.
|
|
|
|
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::vector<Vec3i> &face_neighbors, bool assign_unbound_edges, int *num_edges)
|
|
|
|
{
|
|
|
|
// out elements are not initialized!
|
|
|
|
std::vector<Vec3i> out(face_neighbors.size());
|
|
|
|
int last_edge_id = 0;
|
|
|
|
for (int i = 0; i < int(face_neighbors.size()); ++ i) {
|
|
|
|
const stl_triangle_vertex_indices &triangle = its.indices[i];
|
|
|
|
const Vec3i &neighbors = face_neighbors[i];
|
|
|
|
for (int j = 0; j < 3; ++ j) {
|
|
|
|
int n = neighbors[j];
|
|
|
|
if (n > i) {
|
|
|
|
const stl_triangle_vertex_indices &triangle2 = its.indices[n];
|
|
|
|
int edge_id = last_edge_id ++;
|
|
|
|
Vec2i edge = its_triangle_edge(triangle, j);
|
|
|
|
// First find an edge with opposite orientation.
|
|
|
|
std::swap(edge(0), edge(1));
|
|
|
|
int k = its_triangle_edge_index(triangle2, edge);
|
|
|
|
//FIXME is the following realistic? Could face_neighbors contain such faces?
|
|
|
|
// And if it does, do we want to produce the same edge ID for those mutually incorrectly oriented edges?
|
|
|
|
if (k == -1) {
|
|
|
|
// Second find an edge with the same orientation (the neighbor triangle may be flipped).
|
|
|
|
std::swap(edge(0), edge(1));
|
|
|
|
k = its_triangle_edge_index(triangle2, edge);
|
|
|
|
}
|
|
|
|
assert(k >= 0);
|
|
|
|
out[i](j) = edge_id;
|
|
|
|
out[n](k) = edge_id;
|
|
|
|
} else if (n == -1) {
|
|
|
|
out[i](j) = assign_unbound_edges ? last_edge_id ++ : -1;
|
|
|
|
} else {
|
|
|
|
// Triangle shall never be neighbor of itself.
|
|
|
|
assert(n < i);
|
|
|
|
// Don't do anything, the neighbor will assign us an edge ID in later iterations.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (num_edges)
|
|
|
|
*num_edges = last_edge_id;
|
|
|
|
return out;
|
2018-12-12 09:02:01 +00:00
|
|
|
}
|
|
|
|
|
2021-05-18 15:57:35 +00:00
|
|
|
// Merge duplicate vertices, return number of vertices removed.
|
|
|
|
int its_merge_vertices(indexed_triangle_set &its, bool shrink_to_fit)
|
|
|
|
{
|
|
|
|
// 1) Sort indices to vertices lexicographically by coordinates AND vertex index.
|
|
|
|
auto sorted = reserve_vector<int>(its.vertices.size());
|
|
|
|
for (int i = 0; i < int(its.vertices.size()); ++ i)
|
|
|
|
sorted.emplace_back(i);
|
|
|
|
std::sort(sorted.begin(), sorted.end(), [&its](int il, int ir) {
|
|
|
|
const Vec3f &l = its.vertices[il];
|
|
|
|
const Vec3f &r = its.vertices[ir];
|
|
|
|
// Sort lexicographically by coordinates AND vertex index.
|
|
|
|
return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && (l.z() < r.z() || (l.z() == r.z() && il < ir)))));
|
|
|
|
});
|
|
|
|
|
|
|
|
// 2) Map duplicate vertices to the one with the lowest vertex index.
|
|
|
|
// The vertex to stay will have a map_vertices[...] == -1 index assigned, the other vertices will point to it.
|
|
|
|
std::vector<int> map_vertices(its.vertices.size(), -1);
|
|
|
|
for (int i = 0; i < int(sorted.size());) {
|
|
|
|
const int u = sorted[i];
|
|
|
|
const Vec3f &p = its.vertices[u];
|
|
|
|
int j = i;
|
|
|
|
for (++ j; j < int(sorted.size()); ++ j) {
|
|
|
|
const int v = sorted[j];
|
|
|
|
const Vec3f &q = its.vertices[v];
|
|
|
|
if (p != q)
|
|
|
|
break;
|
|
|
|
assert(v > u);
|
|
|
|
map_vertices[v] = u;
|
|
|
|
}
|
|
|
|
i = j;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3) Shrink its.vertices, update map_vertices with the new vertex indices.
|
|
|
|
int k = 0;
|
|
|
|
for (int i = 0; i < int(its.vertices.size()); ++ i) {
|
|
|
|
if (map_vertices[i] == -1) {
|
|
|
|
map_vertices[i] = k;
|
|
|
|
if (k < i)
|
|
|
|
its.vertices[k] = its.vertices[i];
|
|
|
|
++ k;
|
|
|
|
} else {
|
|
|
|
assert(map_vertices[i] < i);
|
|
|
|
map_vertices[i] = map_vertices[map_vertices[i]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int num_erased = int(its.vertices.size()) - k;
|
|
|
|
|
|
|
|
if (num_erased) {
|
|
|
|
// Shrink the vertices.
|
|
|
|
its.vertices.erase(its.vertices.begin() + k, its.vertices.end());
|
|
|
|
// Remap face indices.
|
|
|
|
for (stl_triangle_vertex_indices &face : its.indices)
|
|
|
|
for (int i = 0; i < 3; ++ i)
|
|
|
|
face(i) = map_vertices[face(i)];
|
|
|
|
// Optionally shrink to fit (reallocate) vertices.
|
|
|
|
if (shrink_to_fit)
|
|
|
|
its.vertices.shrink_to_fit();
|
|
|
|
}
|
|
|
|
|
|
|
|
return num_erased;
|
|
|
|
}
|
|
|
|
|
2021-05-21 15:57:37 +00:00
|
|
|
void its_flip_triangles(indexed_triangle_set &its)
|
|
|
|
{
|
|
|
|
for (stl_triangle_vertex_indices &face : its.indices)
|
|
|
|
std::swap(face(1), face(2));
|
|
|
|
}
|
|
|
|
|
2021-05-17 18:25:59 +00:00
|
|
|
int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit)
|
2018-12-12 09:02:01 +00:00
|
|
|
{
|
2021-11-05 14:32:14 +00:00
|
|
|
auto it = std::remove_if(its.indices.begin(), its.indices.end(), [](auto &face) {
|
|
|
|
return face(0) == face(1) || face(0) == face(2) || face(1) == face(2);
|
|
|
|
});
|
|
|
|
|
|
|
|
int removed = std::distance(it, its.indices.end());
|
|
|
|
its.indices.erase(it, its.indices.end());
|
|
|
|
|
|
|
|
if (removed && shrink_to_fit)
|
|
|
|
its.indices.shrink_to_fit();
|
|
|
|
|
2021-05-17 18:25:59 +00:00
|
|
|
return removed;
|
2014-01-16 10:25:26 +00:00
|
|
|
}
|
|
|
|
|
2021-05-17 18:25:59 +00:00
|
|
|
int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit)
|
2014-04-25 10:40:21 +00:00
|
|
|
{
|
2021-05-17 18:25:59 +00:00
|
|
|
// First used to mark referenced vertices, later used for mapping old vertex index to a new one.
|
|
|
|
std::vector<int> vertex_map(its.vertices.size(), 0);
|
|
|
|
// Mark referenced vertices.
|
|
|
|
for (const stl_triangle_vertex_indices &face : its.indices)
|
|
|
|
for (int i = 0; i < 3; ++ i)
|
|
|
|
vertex_map[face(i)] = 1;
|
|
|
|
// Compactify vertices, update map from old vertex index to a new one.
|
|
|
|
int last = 0;
|
|
|
|
for (int i = 0; i < int(vertex_map.size()); ++ i)
|
|
|
|
if (vertex_map[i]) {
|
|
|
|
if (last < i)
|
|
|
|
its.vertices[last] = its.vertices[i];
|
|
|
|
vertex_map[i] = last ++;
|
2017-02-26 21:17:39 +00:00
|
|
|
}
|
2021-05-17 18:25:59 +00:00
|
|
|
int removed = int(its.vertices.size()) - last;
|
|
|
|
if (removed) {
|
|
|
|
its.vertices.erase(its.vertices.begin() + last, its.vertices.end());
|
|
|
|
// Update faces with the new vertex indices.
|
|
|
|
for (stl_triangle_vertex_indices &face : its.indices)
|
|
|
|
for (int i = 0; i < 3; ++ i)
|
|
|
|
face(i) = vertex_map[face(i)];
|
|
|
|
// Optionally shrink the vertices.
|
|
|
|
if (shrink_to_fit)
|
|
|
|
its.vertices.shrink_to_fit();
|
2018-09-17 13:12:13 +00:00
|
|
|
}
|
2021-05-17 18:25:59 +00:00
|
|
|
return removed;
|
2014-04-25 10:40:21 +00:00
|
|
|
}
|
|
|
|
|
2021-08-16 09:53:37 +00:00
|
|
|
bool its_store_triangle(const indexed_triangle_set &its,
|
|
|
|
const char * obj_filename,
|
|
|
|
size_t triangle_index)
|
|
|
|
{
|
|
|
|
if (its.indices.size() <= triangle_index) return false;
|
|
|
|
Vec3i t = its.indices[triangle_index];
|
|
|
|
indexed_triangle_set its2;
|
|
|
|
its2.indices = {{0, 1, 2}};
|
|
|
|
its2.vertices = {its.vertices[t[0]], its.vertices[t[1]],
|
|
|
|
its.vertices[t[2]]};
|
|
|
|
return its_write_obj(its2, obj_filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool its_store_triangles(const indexed_triangle_set &its,
|
|
|
|
const char * obj_filename,
|
|
|
|
const std::vector<size_t> & triangles)
|
|
|
|
{
|
|
|
|
indexed_triangle_set its2;
|
|
|
|
its2.vertices.reserve(triangles.size() * 3);
|
|
|
|
its2.indices.reserve(triangles.size());
|
|
|
|
std::map<size_t, size_t> vertex_map;
|
|
|
|
for (auto ti : triangles) {
|
|
|
|
if (its.indices.size() <= ti) return false;
|
|
|
|
Vec3i t = its.indices[ti];
|
|
|
|
Vec3i new_t;
|
|
|
|
for (size_t i = 0; i < 3; ++i) {
|
|
|
|
size_t vi = t[i];
|
|
|
|
auto it = vertex_map.find(vi);
|
|
|
|
if (it != vertex_map.end()) {
|
|
|
|
new_t[i] = it->second;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
size_t new_vi = its2.vertices.size();
|
|
|
|
its2.vertices.push_back(its.vertices[vi]);
|
|
|
|
vertex_map[vi] = new_vi;
|
|
|
|
new_t[i] = new_vi;
|
|
|
|
}
|
|
|
|
its2.indices.push_back(new_t);
|
|
|
|
}
|
|
|
|
return its_write_obj(its2, obj_filename);
|
|
|
|
}
|
|
|
|
|
2021-05-17 18:25:59 +00:00
|
|
|
void its_shrink_to_fit(indexed_triangle_set &its)
|
2014-01-07 14:40:38 +00:00
|
|
|
{
|
2021-05-17 18:25:59 +00:00
|
|
|
its.indices.shrink_to_fit();
|
|
|
|
its.vertices.shrink_to_fit();
|
2013-11-23 18:41:40 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 09:35:27 +00:00
|
|
|
template<typename TransformVertex>
|
2021-05-19 11:39:56 +00:00
|
|
|
void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, const TransformVertex &transform_fn, const float z, Points &all_pts)
|
2021-05-19 09:35:27 +00:00
|
|
|
{
|
2021-05-19 11:59:34 +00:00
|
|
|
all_pts.reserve(all_pts.size() + its.indices.size() * 3);
|
2021-05-19 09:35:27 +00:00
|
|
|
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
|
|
|
const Vec3f pts[3] = { transform_fn(its.vertices[tri(0)]), transform_fn(its.vertices[tri(1)]), transform_fn(its.vertices[tri(2)]) };
|
2021-05-20 07:09:19 +00:00
|
|
|
int iprev = 2;
|
2021-05-19 09:35:27 +00:00
|
|
|
for (int iedge = 0; iedge < 3; ++ iedge) {
|
|
|
|
const Vec3f &p1 = pts[iprev];
|
|
|
|
const Vec3f &p2 = pts[iedge];
|
|
|
|
if ((p1.z() < z && p2.z() > z) || (p2.z() < z && p1.z() > z)) {
|
|
|
|
// Edge crosses the z plane. Calculate intersection point with the plane.
|
2021-05-19 14:51:25 +00:00
|
|
|
float t = (z - p1.z()) / (p2.z() - p1.z());
|
|
|
|
all_pts.emplace_back(scaled<coord_t>(p1.x() + (p2.x() - p1.x()) * t), scaled<coord_t>(p1.y() + (p2.y() - p1.y()) * t));
|
2021-05-19 09:35:27 +00:00
|
|
|
}
|
2021-05-20 11:58:27 +00:00
|
|
|
if (p2.z() >= z)
|
2021-05-19 09:35:27 +00:00
|
|
|
all_pts.emplace_back(scaled<coord_t>(p2.x()), scaled<coord_t>(p2.y()));
|
|
|
|
iprev = iedge;
|
|
|
|
}
|
|
|
|
}
|
2021-05-19 11:39:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, const Matrix3f &m, const float z, Points &all_pts)
|
|
|
|
{
|
|
|
|
return its_collect_mesh_projection_points_above(its, [m](const Vec3f &p){ return m * p; }, z, all_pts);
|
|
|
|
}
|
|
|
|
|
|
|
|
void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, const Transform3f &t, const float z, Points &all_pts)
|
|
|
|
{
|
|
|
|
return its_collect_mesh_projection_points_above(its, [t](const Vec3f &p){ return t * p; }, z, all_pts);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename TransformVertex>
|
|
|
|
Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const TransformVertex &transform_fn, const float z)
|
|
|
|
{
|
|
|
|
Points all_pts;
|
|
|
|
its_collect_mesh_projection_points_above(its, transform_fn, z, all_pts);
|
2021-05-19 09:35:27 +00:00
|
|
|
return Geometry::convex_hull(std::move(all_pts));
|
|
|
|
}
|
|
|
|
|
|
|
|
Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Matrix3f &m, const float z)
|
|
|
|
{
|
|
|
|
return its_convex_hull_2d_above(its, [m](const Vec3f &p){ return m * p; }, z);
|
|
|
|
}
|
|
|
|
|
|
|
|
Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Transform3f &t, const float z)
|
|
|
|
{
|
|
|
|
return its_convex_hull_2d_above(its, [t](const Vec3f &p){ return t * p; }, z);
|
|
|
|
}
|
|
|
|
|
2016-11-28 01:15:27 +00:00
|
|
|
// Generate the vertex list for a cube solid of arbitrary size in X/Y/Z.
|
2021-06-01 15:52:11 +00:00
|
|
|
indexed_triangle_set its_make_cube(double xd, double yd, double zd)
|
|
|
|
{
|
|
|
|
auto x = float(xd), y = float(yd), z = float(zd);
|
2021-09-20 15:12:22 +00:00
|
|
|
return {
|
|
|
|
{ {0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {4, 6, 7},
|
|
|
|
{0, 4, 7}, {0, 7, 1}, {1, 7, 6}, {1, 6, 2},
|
|
|
|
{2, 6, 5}, {2, 5, 3}, {4, 0, 3}, {4, 3, 5} },
|
|
|
|
{ {x, y, 0}, {x, 0, 0}, {0, 0, 0}, {0, y, 0},
|
|
|
|
{x, y, z}, {0, y, z}, {0, 0, z}, {x, 0, z} }
|
|
|
|
};
|
2021-06-01 15:52:11 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
indexed_triangle_set its_make_prism(float width, float length, float height)
|
2019-10-15 11:49:28 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
// We need two upward facing triangles
|
|
|
|
float x = width / 2.f, y = length / 2.f;
|
|
|
|
return {
|
|
|
|
{
|
|
|
|
{0, 1, 2}, // side 1
|
|
|
|
{4, 3, 5}, // side 2
|
|
|
|
{1, 4, 2}, {2, 4, 5}, // roof 1
|
|
|
|
{0, 2, 5}, {0, 5, 3}, // roof 2
|
|
|
|
{3, 4, 1}, {3, 1, 0} // bottom
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{-x, -y, 0.f}, {x, -y, 0.f}, {0.f, -y, height},
|
|
|
|
{-x, y, 0.f}, {x, y, 0.f}, {0.f, y, height},
|
|
|
|
}
|
|
|
|
};
|
2016-11-27 22:06:45 +00:00
|
|
|
}
|
2016-11-28 01:15:27 +00:00
|
|
|
|
|
|
|
// Generate the mesh for a cylinder and return it, using
|
|
|
|
// the generated angle to calculate the top mesh triangles.
|
2016-11-28 04:34:10 +00:00
|
|
|
// Default is 360 sides, angle fa is in radians.
|
2021-06-01 15:52:11 +00:00
|
|
|
indexed_triangle_set its_make_cylinder(double r, double h, double fa)
|
2019-05-04 12:03:50 +00:00
|
|
|
{
|
2021-06-01 15:52:11 +00:00
|
|
|
indexed_triangle_set mesh;
|
2021-05-17 18:25:59 +00:00
|
|
|
size_t n_steps = (size_t)ceil(2. * PI / fa);
|
|
|
|
double angle_step = 2. * PI / n_steps;
|
2019-05-04 12:03:50 +00:00
|
|
|
|
2021-06-01 15:52:11 +00:00
|
|
|
auto &vertices = mesh.vertices;
|
|
|
|
auto &facets = mesh.indices;
|
2021-05-17 18:25:59 +00:00
|
|
|
vertices.reserve(2 * n_steps + 2);
|
|
|
|
facets.reserve(4 * n_steps);
|
2016-11-28 01:15:27 +00:00
|
|
|
|
|
|
|
// 2 special vertices, top and bottom center, rest are relative to this
|
2021-06-01 15:52:11 +00:00
|
|
|
vertices.emplace_back(Vec3f(0.f, 0.f, 0.f));
|
|
|
|
vertices.emplace_back(Vec3f(0.f, 0.f, float(h)));
|
2016-11-28 01:15:27 +00:00
|
|
|
|
|
|
|
// for each line along the polygon approximating the top/bottom of the
|
|
|
|
// circle, generate four points and four facets (2 for the wall, 2 for the
|
|
|
|
// top and bottom.
|
|
|
|
// Special case: Last line shares 2 vertices with the first line.
|
2021-06-01 15:52:11 +00:00
|
|
|
Vec2f p = Eigen::Rotation2Df(0.f) * Eigen::Vector2f(0, r);
|
|
|
|
vertices.emplace_back(Vec3f(p(0), p(1), 0.f));
|
|
|
|
vertices.emplace_back(Vec3f(p(0), p(1), float(h)));
|
2021-05-17 18:25:59 +00:00
|
|
|
for (size_t i = 1; i < n_steps; ++i) {
|
2021-06-01 15:52:11 +00:00
|
|
|
p = Eigen::Rotation2Df(angle_step * i) * Eigen::Vector2f(0, float(r));
|
|
|
|
vertices.emplace_back(Vec3f(p(0), p(1), 0.f));
|
|
|
|
vertices.emplace_back(Vec3f(p(0), p(1), float(h)));
|
2019-05-04 12:03:50 +00:00
|
|
|
int id = (int)vertices.size() - 1;
|
2020-03-25 10:07:26 +00:00
|
|
|
facets.emplace_back( 0, id - 1, id - 3); // top
|
|
|
|
facets.emplace_back(id, 1, id - 2); // bottom
|
2021-05-17 18:25:59 +00:00
|
|
|
facets.emplace_back(id, id - 2, id - 3); // upper-right of side
|
2020-03-25 10:07:26 +00:00
|
|
|
facets.emplace_back(id, id - 3, id - 1); // bottom-left of side
|
2016-11-28 01:15:27 +00:00
|
|
|
}
|
2016-11-28 04:34:10 +00:00
|
|
|
// Connect the last set of vertices with the first.
|
2021-05-17 18:25:59 +00:00
|
|
|
int id = (int)vertices.size() - 1;
|
2020-03-25 10:07:26 +00:00
|
|
|
facets.emplace_back( 0, 2, id - 1);
|
|
|
|
facets.emplace_back( 3, 1, id);
|
2021-05-17 18:25:59 +00:00
|
|
|
facets.emplace_back(id, 2, 3);
|
2020-03-25 10:07:26 +00:00
|
|
|
facets.emplace_back(id, id - 1, 2);
|
2021-06-01 15:52:11 +00:00
|
|
|
|
|
|
|
return mesh;
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:51:28 +00:00
|
|
|
indexed_triangle_set its_make_cone(double r, double h, double fa)
|
2021-03-12 09:29:17 +00:00
|
|
|
{
|
2021-09-14 12:51:28 +00:00
|
|
|
indexed_triangle_set mesh;
|
|
|
|
auto& vertices = mesh.vertices;
|
|
|
|
auto& facets = mesh.indices;
|
|
|
|
vertices.reserve(3 + 2 * size_t(2 * PI / fa));
|
|
|
|
|
|
|
|
// base center and top vertex
|
|
|
|
vertices.emplace_back(Vec3f::Zero());
|
|
|
|
vertices.emplace_back(Vec3f(0., 0., h));
|
2021-03-12 09:29:17 +00:00
|
|
|
|
|
|
|
size_t i = 0;
|
|
|
|
for (double angle=0; angle<2*PI; angle+=fa) {
|
|
|
|
vertices.emplace_back(r*std::cos(angle), r*std::sin(angle), 0.);
|
|
|
|
if (angle > 0.) {
|
|
|
|
facets.emplace_back(0, i+2, i+1);
|
|
|
|
facets.emplace_back(1, i+1, i+2);
|
|
|
|
}
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
facets.emplace_back(0, 2, i+1); // close the shape
|
|
|
|
facets.emplace_back(1, i+1, 2);
|
|
|
|
|
|
|
|
return mesh;
|
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
indexed_triangle_set its_make_pyramid(float base, float height)
|
2021-09-14 12:51:28 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
float a = base / 2.f;
|
|
|
|
return {
|
|
|
|
{
|
|
|
|
{0, 1, 2},
|
|
|
|
{0, 2, 3},
|
|
|
|
{0, 1, 4},
|
|
|
|
{1, 2, 4},
|
|
|
|
{2, 3, 4},
|
|
|
|
{3, 0, 4}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{-a, -a, 0}, {a, -a, 0}, {a, a, 0},
|
|
|
|
{-a, a, 0}, {0.f, 0.f, height}
|
|
|
|
}
|
|
|
|
};
|
2021-09-14 12:51:28 +00:00
|
|
|
}
|
2021-03-12 09:29:17 +00:00
|
|
|
|
2016-12-05 22:43:55 +00:00
|
|
|
// Generates mesh for a sphere centered about the origin, using the generated angle
|
|
|
|
// to determine the granularity.
|
|
|
|
// Default angle is 1 degree.
|
2019-05-04 12:03:50 +00:00
|
|
|
//FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html
|
2021-06-01 15:52:11 +00:00
|
|
|
indexed_triangle_set its_make_sphere(double radius, double fa)
|
2019-05-04 12:03:50 +00:00
|
|
|
{
|
2021-05-17 18:25:59 +00:00
|
|
|
int sectorCount = int(ceil(2. * M_PI / fa));
|
|
|
|
int stackCount = int(ceil(M_PI / fa));
|
|
|
|
float sectorStep = float(2. * M_PI / sectorCount);
|
|
|
|
float stackStep = float(M_PI / stackCount);
|
|
|
|
|
2021-06-01 15:52:11 +00:00
|
|
|
indexed_triangle_set mesh;
|
|
|
|
auto& vertices = mesh.vertices;
|
2021-05-17 18:25:59 +00:00
|
|
|
vertices.reserve((stackCount - 1) * sectorCount + 2);
|
|
|
|
for (int i = 0; i <= stackCount; ++ i) {
|
|
|
|
// from pi/2 to -pi/2
|
|
|
|
double stackAngle = 0.5 * M_PI - stackStep * i;
|
|
|
|
double xy = radius * cos(stackAngle);
|
|
|
|
double z = radius * sin(stackAngle);
|
|
|
|
if (i == 0 || i == stackCount)
|
2021-06-01 15:52:11 +00:00
|
|
|
vertices.emplace_back(Vec3f(float(xy), 0.f, float(z)));
|
2021-05-17 18:25:59 +00:00
|
|
|
else
|
|
|
|
for (int j = 0; j < sectorCount; ++ j) {
|
|
|
|
// from 0 to 2pi
|
|
|
|
double sectorAngle = sectorStep * j;
|
2021-06-01 15:52:11 +00:00
|
|
|
vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast<float>());
|
2021-05-17 18:25:59 +00:00
|
|
|
}
|
2021-03-03 08:29:13 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 15:52:11 +00:00
|
|
|
auto& facets = mesh.indices;
|
2021-05-17 18:25:59 +00:00
|
|
|
facets.reserve(2 * (stackCount - 1) * sectorCount);
|
|
|
|
for (int i = 0; i < stackCount; ++ i) {
|
|
|
|
// Beginning of current stack.
|
|
|
|
int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount);
|
|
|
|
int k1_first = k1;
|
|
|
|
// Beginning of next stack.
|
|
|
|
int k2 = (i == 0) ? 1 : (k1 + sectorCount);
|
|
|
|
int k2_first = k2;
|
|
|
|
for (int j = 0; j < sectorCount; ++ j) {
|
|
|
|
// 2 triangles per sector excluding first and last stacks
|
|
|
|
int k1_next = k1;
|
|
|
|
int k2_next = k2;
|
|
|
|
if (i != 0) {
|
|
|
|
k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1);
|
|
|
|
facets.emplace_back(k1, k2, k1_next);
|
|
|
|
}
|
|
|
|
if (i + 1 != stackCount) {
|
|
|
|
k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1);
|
|
|
|
facets.emplace_back(k1_next, k2, k2_next);
|
|
|
|
}
|
|
|
|
k1 = k1_next;
|
|
|
|
k2 = k2_next;
|
|
|
|
}
|
|
|
|
}
|
2021-06-01 15:52:11 +00:00
|
|
|
|
|
|
|
return mesh;
|
|
|
|
}
|
|
|
|
|
2021-10-22 12:02:39 +00:00
|
|
|
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts)
|
|
|
|
{
|
|
|
|
std::vector<Vec3f> dst_vertices;
|
|
|
|
std::vector<Vec3i> dst_facets;
|
|
|
|
|
|
|
|
if (! pts.empty()) {
|
|
|
|
// The qhull call:
|
|
|
|
orgQhull::Qhull qhull;
|
|
|
|
qhull.disableOutputStream(); // we want qhull to be quiet
|
2021-10-22 13:20:40 +00:00
|
|
|
#if ! REALfloat
|
2021-10-22 12:02:39 +00:00
|
|
|
std::vector<realT> src_vertices;
|
2021-10-22 13:20:40 +00:00
|
|
|
#endif
|
2021-10-22 12:02:39 +00:00
|
|
|
try {
|
|
|
|
#if REALfloat
|
|
|
|
qhull.runQhull("", 3, (int)pts.size(), (const realT*)(pts.front().data()), "Qt");
|
|
|
|
#else
|
2021-10-22 13:20:40 +00:00
|
|
|
src_vertices.reserve(pts.size() * 3);
|
2021-10-22 12:02:39 +00:00
|
|
|
// We will now fill the vector with input points for computation:
|
2021-10-22 13:20:40 +00:00
|
|
|
for (const stl_vertex &v : pts)
|
2021-10-22 12:02:39 +00:00
|
|
|
for (int i = 0; i < 3; ++ i)
|
|
|
|
src_vertices.emplace_back(v(i));
|
|
|
|
qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt");
|
|
|
|
#endif
|
|
|
|
} catch (...) {
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "its_convex_hull: Unable to create convex hull";
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Let's collect results:
|
|
|
|
// Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices).
|
|
|
|
std::vector<int> map_dst_vertices;
|
|
|
|
#ifndef NDEBUG
|
|
|
|
Vec3f centroid = Vec3f::Zero();
|
2021-10-22 13:11:42 +00:00
|
|
|
for (const stl_vertex& pt : pts)
|
2021-10-22 12:02:39 +00:00
|
|
|
centroid += pt;
|
2021-10-22 13:11:42 +00:00
|
|
|
centroid /= float(pts.size());
|
2021-10-22 12:02:39 +00:00
|
|
|
#endif // NDEBUG
|
2021-11-30 09:50:18 +00:00
|
|
|
for (const orgQhull::QhullFacet &facet : qhull.facetList()) {
|
2021-10-22 12:02:39 +00:00
|
|
|
// Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID.
|
|
|
|
Vec3i indices;
|
|
|
|
int cnt = 0;
|
|
|
|
for (const orgQhull::QhullVertex vertex : facet.vertices()) {
|
|
|
|
int id = vertex.id();
|
|
|
|
assert(id >= 0);
|
|
|
|
if (id >= int(map_dst_vertices.size()))
|
|
|
|
map_dst_vertices.resize(next_highest_power_of_2(size_t(id + 1)), -1);
|
|
|
|
if (int i = map_dst_vertices[id]; i == -1) {
|
|
|
|
// Allocate a new vertex.
|
|
|
|
i = int(dst_vertices.size());
|
|
|
|
map_dst_vertices[id] = i;
|
|
|
|
orgQhull::QhullPoint pt(vertex.point());
|
|
|
|
dst_vertices.emplace_back(pt[0], pt[1], pt[2]);
|
|
|
|
indices[cnt] = i;
|
|
|
|
} else {
|
|
|
|
// Reuse existing vertex.
|
|
|
|
indices[cnt] = i;
|
|
|
|
}
|
|
|
|
if (cnt ++ == 3)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
assert(cnt == 3);
|
|
|
|
if (cnt == 3) {
|
|
|
|
// QHull sorts vertices of a face lexicographically by their IDs, not by face normals.
|
|
|
|
// Calculate face normal based on the order of vertices.
|
|
|
|
Vec3f n = (dst_vertices[indices(1)] - dst_vertices[indices(0)]).cross(dst_vertices[indices(2)] - dst_vertices[indices(1)]);
|
|
|
|
auto *n2 = facet.getBaseT()->normal;
|
|
|
|
auto d = n.x() * n2[0] + n.y() * n2[1] + n.z() * n2[2];
|
|
|
|
#ifndef NDEBUG
|
|
|
|
Vec3f n3 = (dst_vertices[indices(0)] - centroid);
|
|
|
|
auto d3 = n.dot(n3);
|
|
|
|
assert((d < 0.f) == (d3 < 0.f));
|
|
|
|
#endif // NDEBUG
|
|
|
|
// Get the face normal from QHull.
|
|
|
|
if (d < 0.f)
|
|
|
|
// Fix face orientation.
|
|
|
|
std::swap(indices[1], indices[2]);
|
|
|
|
dst_facets.emplace_back(indices);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { std::move(dst_facets), std::move(dst_vertices) };
|
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
void its_reverse_all_facets(indexed_triangle_set &its)
|
2021-06-01 15:52:11 +00:00
|
|
|
{
|
2021-09-20 15:12:22 +00:00
|
|
|
for (stl_triangle_vertex_indices &face : its.indices)
|
|
|
|
std::swap(face[0], face[1]);
|
2021-03-03 08:29:13 +00:00
|
|
|
}
|
|
|
|
|
2021-05-21 12:08:05 +00:00
|
|
|
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B)
|
|
|
|
{
|
|
|
|
auto N = int(A.vertices.size());
|
|
|
|
auto N_f = A.indices.size();
|
|
|
|
|
|
|
|
A.vertices.insert(A.vertices.end(), B.vertices.begin(), B.vertices.end());
|
|
|
|
A.indices.insert(A.indices.end(), B.indices.begin(), B.indices.end());
|
|
|
|
|
|
|
|
for(size_t n = N_f; n < A.indices.size(); n++)
|
|
|
|
A.indices[n] += Vec3i{N, N, N};
|
|
|
|
}
|
|
|
|
|
|
|
|
void its_merge(indexed_triangle_set &A, const std::vector<Vec3f> &triangles)
|
|
|
|
{
|
|
|
|
const size_t offs = A.vertices.size();
|
|
|
|
A.vertices.insert(A.vertices.end(), triangles.begin(), triangles.end());
|
|
|
|
A.indices.reserve(A.indices.size() + A.vertices.size() / 3);
|
|
|
|
|
|
|
|
for(int i = int(offs); i < int(A.vertices.size()); i += 3)
|
|
|
|
A.indices.emplace_back(i, i + 1, i + 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void its_merge(indexed_triangle_set &A, const Pointf3s &triangles)
|
|
|
|
{
|
|
|
|
auto trianglesf = reserve_vector<Vec3f> (triangles.size());
|
|
|
|
for (auto &t : triangles)
|
|
|
|
trianglesf.emplace_back(t.cast<float>());
|
|
|
|
|
|
|
|
its_merge(A, trianglesf);
|
|
|
|
}
|
|
|
|
|
2021-05-26 14:41:34 +00:00
|
|
|
float its_volume(const indexed_triangle_set &its)
|
|
|
|
{
|
|
|
|
if (its.empty()) return 0.;
|
|
|
|
|
|
|
|
// Choose a point, any point as the reference.
|
|
|
|
auto p0 = its.vertices.front();
|
|
|
|
float volume = 0.f;
|
|
|
|
for (size_t i = 0; i < its.indices.size(); ++ i) {
|
|
|
|
// Do dot product to get distance from point to plane.
|
|
|
|
its_triangle triangle = its_triangle_vertices(its, i);
|
|
|
|
Vec3f U = triangle[1] - triangle[0];
|
|
|
|
Vec3f V = triangle[2] - triangle[0];
|
|
|
|
Vec3f C = U.cross(V);
|
|
|
|
Vec3f normal = C.normalized();
|
|
|
|
float area = 0.5 * C.norm();
|
|
|
|
float height = normal.dot(triangle[0] - p0);
|
|
|
|
volume += (area * height) / 3.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
return volume;
|
|
|
|
}
|
|
|
|
|
2021-08-27 19:04:11 +00:00
|
|
|
float its_average_edge_length(const indexed_triangle_set &its)
|
|
|
|
{
|
|
|
|
if (its.indices.empty())
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
double edge_length = 0.f;
|
|
|
|
for (size_t i = 0; i < its.indices.size(); ++ i) {
|
|
|
|
const its_triangle v = its_triangle_vertices(its, i);
|
|
|
|
edge_length += (v[1] - v[0]).cast<double>().norm() +
|
|
|
|
(v[2] - v[0]).cast<double>().norm() +
|
|
|
|
(v[1] - v[2]).cast<double>().norm();
|
|
|
|
}
|
|
|
|
return float(edge_length / (3 * its.indices.size()));
|
|
|
|
}
|
|
|
|
|
2021-06-01 13:49:19 +00:00
|
|
|
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its)
|
2021-05-26 14:41:34 +00:00
|
|
|
{
|
2021-06-01 13:49:19 +00:00
|
|
|
return its_split<>(its);
|
2021-05-28 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
// Number of disconnected patches (faces are connected if they share an edge, shared edge defined with 2 shared vertex indices).
|
2021-11-11 09:02:58 +00:00
|
|
|
size_t its_number_of_patches(const indexed_triangle_set &its)
|
2021-09-20 15:12:22 +00:00
|
|
|
{
|
|
|
|
return its_number_of_patches<>(its);
|
|
|
|
}
|
2021-11-11 09:02:58 +00:00
|
|
|
size_t its_number_of_patches(const indexed_triangle_set &its, const std::vector<Vec3i> &face_neighbors)
|
2021-09-20 15:12:22 +00:00
|
|
|
{
|
|
|
|
return its_number_of_patches<>(ItsNeighborsWrapper{ its, face_neighbors });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Same as its_number_of_patches(its) > 1, but faster.
|
2021-06-01 13:49:19 +00:00
|
|
|
bool its_is_splittable(const indexed_triangle_set &its)
|
2021-05-28 12:55:25 +00:00
|
|
|
{
|
2021-06-01 13:49:19 +00:00
|
|
|
return its_is_splittable<>(its);
|
2021-05-28 12:55:25 +00:00
|
|
|
}
|
2021-09-20 15:12:22 +00:00
|
|
|
bool its_is_splittable(const indexed_triangle_set &its, const std::vector<Vec3i> &face_neighbors)
|
|
|
|
{
|
|
|
|
return its_is_splittable<>(ItsNeighborsWrapper{ its, face_neighbors });
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t its_num_open_edges(const std::vector<Vec3i> &face_neighbors)
|
|
|
|
{
|
|
|
|
size_t num_open_edges = 0;
|
|
|
|
for (Vec3i neighbors : face_neighbors)
|
|
|
|
for (int n : neighbors)
|
|
|
|
if (n < 0)
|
|
|
|
++ num_open_edges;
|
|
|
|
return num_open_edges;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t its_num_open_edges(const indexed_triangle_set &its)
|
|
|
|
{
|
|
|
|
return its_num_open_edges(its_face_neighbors(its));
|
|
|
|
}
|
2021-05-28 12:55:25 +00:00
|
|
|
|
2021-06-02 13:45:11 +00:00
|
|
|
void VertexFaceIndex::create(const indexed_triangle_set &its)
|
2021-05-28 12:55:25 +00:00
|
|
|
{
|
2021-06-02 13:45:11 +00:00
|
|
|
m_vertex_to_face_start.assign(its.vertices.size() + 1, 0);
|
|
|
|
// 1) Calculate vertex incidence by scatter.
|
|
|
|
for (auto &face : its.indices) {
|
|
|
|
++ m_vertex_to_face_start[face(0) + 1];
|
|
|
|
++ m_vertex_to_face_start[face(1) + 1];
|
|
|
|
++ m_vertex_to_face_start[face(2) + 1];
|
|
|
|
}
|
|
|
|
// 2) Prefix sum to calculate offsets to m_vertex_faces_all.
|
|
|
|
for (size_t i = 2; i < m_vertex_to_face_start.size(); ++ i)
|
|
|
|
m_vertex_to_face_start[i] += m_vertex_to_face_start[i - 1];
|
|
|
|
// 3) Scatter indices of faces incident to a vertex into m_vertex_faces_all.
|
|
|
|
m_vertex_faces_all.assign(m_vertex_to_face_start.back(), 0);
|
|
|
|
for (size_t face_idx = 0; face_idx < its.indices.size(); ++ face_idx) {
|
|
|
|
auto &face = its.indices[face_idx];
|
|
|
|
for (int i = 0; i < 3; ++ i)
|
|
|
|
m_vertex_faces_all[m_vertex_to_face_start[face(i)] ++] = face_idx;
|
|
|
|
}
|
|
|
|
// 4) The previous loop modified m_vertex_to_face_start. Revert the change.
|
|
|
|
for (auto i = int(m_vertex_to_face_start.size()) - 1; i > 0; -- i)
|
|
|
|
m_vertex_to_face_start[i] = m_vertex_to_face_start[i - 1];
|
|
|
|
m_vertex_to_face_start.front() = 0;
|
|
|
|
}
|
2021-05-28 12:55:25 +00:00
|
|
|
|
2021-06-20 13:21:12 +00:00
|
|
|
std::vector<Vec3i> its_face_neighbors(const indexed_triangle_set &its)
|
2021-06-02 13:45:11 +00:00
|
|
|
{
|
2021-06-20 13:21:12 +00:00
|
|
|
return create_face_neighbors_index(ex_seq, its);
|
2021-06-02 13:45:11 +00:00
|
|
|
}
|
|
|
|
|
2021-06-20 13:21:12 +00:00
|
|
|
std::vector<Vec3i> its_face_neighbors_par(const indexed_triangle_set &its)
|
2021-06-02 13:45:11 +00:00
|
|
|
{
|
2021-06-20 13:21:12 +00:00
|
|
|
return create_face_neighbors_index(ex_tbb, its);
|
2021-05-26 14:41:34 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 09:58:07 +00:00
|
|
|
std::vector<Vec3f> its_face_normals(const indexed_triangle_set &its)
|
|
|
|
{
|
|
|
|
std::vector<Vec3f> normals;
|
|
|
|
normals.reserve(its.indices.size());
|
2021-09-14 12:51:28 +00:00
|
|
|
for (stl_triangle_vertex_indices face : its.indices)
|
|
|
|
normals.push_back(its_face_normal(its, face));
|
2021-09-14 09:58:07 +00:00
|
|
|
return normals;
|
|
|
|
}
|
|
|
|
|
2021-09-20 15:12:22 +00:00
|
|
|
#if BOOST_ENDIAN_LITTLE_BYTE
|
|
|
|
static inline void big_endian_reverse_quads(char*, size_t) {}
|
|
|
|
#else // BOOST_ENDIAN_LITTLE_BYTE
|
|
|
|
static inline void big_endian_reverse_quads(char *buf, size_t cnt)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < cnt; i += 4) {
|
|
|
|
std::swap(buf[i], buf[i+3]);
|
|
|
|
std::swap(buf[i+1], buf[i+2]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // BOOST_ENDIAN_LITTLE_BYTE
|
|
|
|
|
|
|
|
bool its_write_stl_ascii(const char *file, const char *label, const std::vector<stl_triangle_vertex_indices> &indices, const std::vector<stl_vertex> &vertices)
|
|
|
|
{
|
|
|
|
FILE *fp = boost::nowide::fopen(file, "w");
|
|
|
|
if (fp == nullptr) {
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "its_write_stl_ascii: Couldn't open " << file << " for writing";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(fp, "solid %s\n", label);
|
|
|
|
|
2021-10-01 12:31:22 +00:00
|
|
|
for (const stl_triangle_vertex_indices& face : indices) {
|
2021-09-20 15:12:22 +00:00
|
|
|
Vec3f vertex[3] = { vertices[face(0)], vertices[face(1)], vertices[face(2)] };
|
|
|
|
Vec3f normal = (vertex[1] - vertex[0]).cross(vertex[2] - vertex[1]).normalized();
|
|
|
|
fprintf(fp, " facet normal % .8E % .8E % .8E\n", normal(0), normal(1), normal(2));
|
|
|
|
fprintf(fp, " outer loop\n");
|
|
|
|
fprintf(fp, " vertex % .8E % .8E % .8E\n", vertex[0](0), vertex[0](1), vertex[0](2));
|
|
|
|
fprintf(fp, " vertex % .8E % .8E % .8E\n", vertex[1](0), vertex[1](1), vertex[1](2));
|
|
|
|
fprintf(fp, " vertex % .8E % .8E % .8E\n", vertex[2](0), vertex[2](1), vertex[2](2));
|
|
|
|
fprintf(fp, " endloop\n");
|
|
|
|
fprintf(fp, " endfacet\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(fp, "endsolid %s\n", label);
|
|
|
|
fclose(fp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool its_write_stl_binary(const char *file, const char *label, const std::vector<stl_triangle_vertex_indices> &indices, const std::vector<stl_vertex> &vertices)
|
|
|
|
{
|
|
|
|
FILE *fp = boost::nowide::fopen(file, "wb");
|
|
|
|
if (fp == nullptr) {
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "its_write_stl_binary: Couldn't open " << file << " for writing";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
static constexpr const int header_size = 80;
|
|
|
|
std::vector<char> header(header_size, 0);
|
|
|
|
if (int header_len = std::min((label == nullptr) ? 0 : int(strlen(label)), header_size); header_len > 0)
|
|
|
|
::memcpy(header.data(), label, header_len);
|
|
|
|
::fwrite(header.data(), header_size, 1, fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t nfaces = indices.size();
|
|
|
|
big_endian_reverse_quads(reinterpret_cast<char*>(&nfaces), 4);
|
|
|
|
::fwrite(&nfaces, 4, 1, fp);
|
|
|
|
|
|
|
|
stl_facet f;
|
|
|
|
f.extra[0] = 0;
|
|
|
|
f.extra[1] = 0;
|
2021-10-01 12:31:22 +00:00
|
|
|
for (const stl_triangle_vertex_indices& face : indices) {
|
2021-09-20 15:12:22 +00:00
|
|
|
f.vertex[0] = vertices[face(0)];
|
|
|
|
f.vertex[1] = vertices[face(1)];
|
|
|
|
f.vertex[2] = vertices[face(2)];
|
|
|
|
f.normal = (f.vertex[1] - f.vertex[0]).cross(f.vertex[2] - f.vertex[1]).normalized();
|
|
|
|
big_endian_reverse_quads(reinterpret_cast<char*>(&f), 48);
|
|
|
|
fwrite(&f, 50, 1, fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(fp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-26 14:41:34 +00:00
|
|
|
} // namespace Slic3r
|