Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_gcode_viewer
This commit is contained in:
commit
80c2f107c1
@ -2,3 +2,4 @@
|
||||
#add_subdirectory(openvdb)
|
||||
add_subdirectory(meshboolean)
|
||||
add_subdirectory(opencsg)
|
||||
#add_subdirectory(aabb-evaluation)
|
2
sandboxes/aabb-evaluation/CMakeLists.txt
Normal file
2
sandboxes/aabb-evaluation/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
add_executable(aabb-evaluation aabb-evaluation.cpp)
|
||||
target_link_libraries(aabb-evaluation libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_DL_LIBS})
|
224
sandboxes/aabb-evaluation/aabb-evaluation.cpp
Normal file
224
sandboxes/aabb-evaluation/aabb-evaluation.cpp
Normal file
@ -0,0 +1,224 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
|
||||
#include <Shiny/Shiny.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
#include <igl/ray_mesh_intersect.h>
|
||||
#include <igl/point_mesh_squared_distance.h>
|
||||
#include <igl/remove_duplicate_vertices.h>
|
||||
#include <igl/signed_distance.h>
|
||||
#include <igl/random_dir.h>
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
const std::string USAGE_STR = {
|
||||
"Usage: aabb-evaluation stlfilename.stl"
|
||||
};
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
void profile(const TriangleMesh &mesh)
|
||||
{
|
||||
Eigen::MatrixXd V;
|
||||
Eigen::MatrixXi F;
|
||||
Eigen::MatrixXd vertex_normals;
|
||||
sla::to_eigen_mesh(mesh, V, F);
|
||||
igl::per_vertex_normals(V, F, vertex_normals);
|
||||
|
||||
static constexpr int num_samples = 100;
|
||||
const int num_vertices = std::min(10000, int(mesh.its.vertices.size()));
|
||||
const Eigen::MatrixXd dirs = igl::random_dir_stratified(num_samples).cast<double>();
|
||||
|
||||
Eigen::MatrixXd occlusion_output0;
|
||||
{
|
||||
AABBTreeIndirect::Tree3f tree;
|
||||
{
|
||||
PROFILE_BLOCK(AABBIndirect_Init);
|
||||
tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(mesh.its.vertices, mesh.its.indices);
|
||||
}
|
||||
{
|
||||
PROFILE_BLOCK(EigenMesh3D_AABBIndirectF_AmbientOcclusion);
|
||||
occlusion_output0.resize(num_vertices, 1);
|
||||
for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) {
|
||||
const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast<double>();
|
||||
const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast<double>();
|
||||
int num_hits = 0;
|
||||
for (int s = 0; s < num_samples; s++) {
|
||||
Eigen::Vector3d d = dirs.row(s);
|
||||
if(d.dot(normal) < 0) {
|
||||
// reverse ray
|
||||
d *= -1;
|
||||
}
|
||||
igl::Hit hit;
|
||||
if (AABBTreeIndirect::intersect_ray_first_hit(mesh.its.vertices, mesh.its.indices, tree, (origin + 1e-4 * d).eval(), d, hit))
|
||||
++ num_hits;
|
||||
}
|
||||
occlusion_output0(ivertex) = (double)num_hits/(double)num_samples;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_BLOCK(EigenMesh3D_AABBIndirectFF_AmbientOcclusion);
|
||||
occlusion_output0.resize(num_vertices, 1);
|
||||
for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) {
|
||||
const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast<double>();
|
||||
const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast<double>();
|
||||
int num_hits = 0;
|
||||
for (int s = 0; s < num_samples; s++) {
|
||||
Eigen::Vector3d d = dirs.row(s);
|
||||
if(d.dot(normal) < 0) {
|
||||
// reverse ray
|
||||
d *= -1;
|
||||
}
|
||||
igl::Hit hit;
|
||||
if (AABBTreeIndirect::intersect_ray_first_hit(mesh.its.vertices, mesh.its.indices, tree,
|
||||
Eigen::Vector3f((origin + 1e-4 * d).template cast<float>()),
|
||||
Eigen::Vector3f(d.template cast<float>()), hit))
|
||||
++ num_hits;
|
||||
}
|
||||
occlusion_output0(ivertex) = (double)num_hits/(double)num_samples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Eigen::MatrixXd occlusion_output1;
|
||||
{
|
||||
std::vector<Vec3d> vertices;
|
||||
std::vector<Vec3i> triangles;
|
||||
for (int i = 0; i < V.rows(); ++ i)
|
||||
vertices.emplace_back(V.row(i).transpose());
|
||||
for (int i = 0; i < F.rows(); ++ i)
|
||||
triangles.emplace_back(F.row(i).transpose());
|
||||
AABBTreeIndirect::Tree3d tree;
|
||||
{
|
||||
PROFILE_BLOCK(AABBIndirectD_Init);
|
||||
tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(vertices, triangles);
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_BLOCK(EigenMesh3D_AABBIndirectD_AmbientOcclusion);
|
||||
occlusion_output1.resize(num_vertices, 1);
|
||||
for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) {
|
||||
const Eigen::Vector3d origin = V.row(ivertex).template cast<double>();
|
||||
const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast<double>();
|
||||
int num_hits = 0;
|
||||
for (int s = 0; s < num_samples; s++) {
|
||||
Eigen::Vector3d d = dirs.row(s);
|
||||
if(d.dot(normal) < 0) {
|
||||
// reverse ray
|
||||
d *= -1;
|
||||
}
|
||||
igl::Hit hit;
|
||||
if (AABBTreeIndirect::intersect_ray_first_hit(vertices, triangles, tree, Eigen::Vector3d(origin + 1e-4 * d), d, hit))
|
||||
++ num_hits;
|
||||
}
|
||||
occlusion_output1(ivertex) = (double)num_hits/(double)num_samples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the AABB accelaration tree
|
||||
|
||||
Eigen::MatrixXd occlusion_output2;
|
||||
{
|
||||
igl::AABB<Eigen::MatrixXd, 3> AABB;
|
||||
{
|
||||
PROFILE_BLOCK(EigenMesh3D_AABB_Init);
|
||||
AABB.init(V, F);
|
||||
}
|
||||
{
|
||||
PROFILE_BLOCK(EigenMesh3D_AABB_AmbientOcclusion);
|
||||
occlusion_output2.resize(num_vertices, 1);
|
||||
for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) {
|
||||
const Eigen::Vector3d origin = V.row(ivertex).template cast<double>();
|
||||
const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast<double>();
|
||||
int num_hits = 0;
|
||||
for (int s = 0; s < num_samples; s++) {
|
||||
Eigen::Vector3d d = dirs.row(s);
|
||||
if(d.dot(normal) < 0) {
|
||||
// reverse ray
|
||||
d *= -1;
|
||||
}
|
||||
igl::Hit hit;
|
||||
if (AABB.intersect_ray(V, F, origin + 1e-4 * d, d, hit))
|
||||
++ num_hits;
|
||||
}
|
||||
occlusion_output2(ivertex) = (double)num_hits/(double)num_samples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Eigen::MatrixXd occlusion_output3;
|
||||
{
|
||||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||
igl::AABB<MapMatrixXfUnaligned, 3> AABB;
|
||||
auto vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), mesh.its.vertices.size(), 3);
|
||||
auto faces = MapMatrixXiUnaligned(mesh.its.indices.front().data(), mesh.its.indices.size(), 3);
|
||||
{
|
||||
PROFILE_BLOCK(EigenMesh3D_AABBf_Init);
|
||||
AABB.init(
|
||||
vertices,
|
||||
faces);
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_BLOCK(EigenMesh3D_AABBf_AmbientOcclusion);
|
||||
occlusion_output3.resize(num_vertices, 1);
|
||||
for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) {
|
||||
const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast<double>();
|
||||
const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast<double>();
|
||||
int num_hits = 0;
|
||||
for (int s = 0; s < num_samples; s++) {
|
||||
Eigen::Vector3d d = dirs.row(s);
|
||||
if(d.dot(normal) < 0) {
|
||||
// reverse ray
|
||||
d *= -1;
|
||||
}
|
||||
igl::Hit hit;
|
||||
if (AABB.intersect_ray(vertices, faces, (origin + 1e-4 * d).eval().template cast<float>(), d.template cast<float>(), hit))
|
||||
++ num_hits;
|
||||
}
|
||||
occlusion_output3(ivertex) = (double)num_hits/(double)num_samples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PROFILE_UPDATE();
|
||||
PROFILE_OUTPUT(nullptr);
|
||||
}
|
||||
|
||||
int main(const int argc, const char *argv[])
|
||||
{
|
||||
if(argc < 2) {
|
||||
std::cout << USAGE_STR << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
TriangleMesh mesh;
|
||||
if (! mesh.ReadSTLFile(argv[1])) {
|
||||
std::cerr << "Error loading " << argv[1] << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
mesh.repair();
|
||||
if (mesh.facets_count() == 0) {
|
||||
std::cerr << "Error loading " << argv[1] << " . It is empty." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
profile(mesh);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -40,8 +40,8 @@ THE SOFTWARE.
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
#define OUTPUT_WIDTH_CALL 6
|
||||
#define OUTPUT_WIDTH_TIME 6
|
||||
#define OUTPUT_WIDTH_PERC 4
|
||||
#define OUTPUT_WIDTH_TIME (6+3)
|
||||
#define OUTPUT_WIDTH_PERC (4+3)
|
||||
#define OUTPUT_WIDTH_SUM 120
|
||||
|
||||
#define OUTPUT_WIDTH_DATA (1+OUTPUT_WIDTH_CALL + 1 + 2*(OUTPUT_WIDTH_TIME+4+OUTPUT_WIDTH_PERC+1) + 1)
|
||||
@ -70,7 +70,7 @@ SHINY_INLINE char* printData(char *output, const ShinyData *a_data, float a_tope
|
||||
const ShinyTimeUnit *totalUnit = ShinyGetTimeUnit(totalTicksAvg);
|
||||
|
||||
snprintf(output, OUTPUT_WIDTH_DATA + TRAILING,
|
||||
" %*.1f %*.0f %-2s %*.0f%% %*.0f %-2s %*.0f%%",
|
||||
" %*.1f %*.2f %-2s %*.2f%% %*.2f %-2s %*.2f%%",
|
||||
OUTPUT_WIDTH_CALL, a_data->entryCount.avg,
|
||||
OUTPUT_WIDTH_TIME, a_data->selfTicks.avg * selfUnit->invTickFreq, selfUnit->suffix,
|
||||
OUTPUT_WIDTH_PERC, a_data->selfTicks.avg * a_topercent,
|
||||
|
@ -7,3 +7,7 @@ add_library(clipper STATIC
|
||||
clipper_z.cpp
|
||||
clipper_z.hpp
|
||||
)
|
||||
|
||||
if(SLIC3R_PROFILE)
|
||||
target_link_libraries(clipper Shiny)
|
||||
endif()
|
||||
|
698
src/libslic3r/AABBTreeIndirect.hpp
Normal file
698
src/libslic3r/AABBTreeIndirect.hpp
Normal file
@ -0,0 +1,698 @@
|
||||
// AABB tree built upon external data set, referencing the external data by integer indices.
|
||||
// The AABB tree balancing and traversal (ray casting, closest triangle of an indexed triangle mesh)
|
||||
// were adapted from libigl AABB.{cpp,hpp} Copyright (C) 2015 Alec Jacobson <alecjacobson@gmail.com>
|
||||
// while the implicit balanced tree representation and memory optimizations are Vojtech's.
|
||||
|
||||
#ifndef slic3r_AABBTreeIndirect_hpp_
|
||||
#define slic3r_AABBTreeIndirect_hpp_
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "Utils.hpp" // for next_highest_power_of_2()
|
||||
|
||||
extern "C"
|
||||
{
|
||||
// Ray-Triangle Intersection Test Routines by Tomas Moller, May 2000
|
||||
#include <igl/raytri.c>
|
||||
}
|
||||
// Definition of the ray intersection hit structure.
|
||||
#include <igl/Hit.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace AABBTreeIndirect {
|
||||
|
||||
// Static balanced AABB tree for raycasting and closest triangle search.
|
||||
// The balanced tree is built over a single large std::vector of nodes, where the children of nodes
|
||||
// are addressed implicitely using a power of two indexing rule.
|
||||
// Memory for a full balanced tree is allocated, but not all nodes at the last level are used.
|
||||
// This may seem like a waste of memory, but one saves memory for the node links and there is zero
|
||||
// overhead of a memory allocator management (usually the memory allocator adds at least one pointer
|
||||
// before the memory returned). However, allocating memory in a single vector is very fast even
|
||||
// in multi-threaded environment and it is cache friendly.
|
||||
//
|
||||
// A balanced tree is built upon a vector of bounding boxes and their centroids, storing the reference
|
||||
// to the source entity (a 3D triangle, a 2D segment etc, a 3D or 2D point etc).
|
||||
// The source bounding boxes may have an epsilon applied to fight numeric rounding errors when
|
||||
// traversing the AABB tree.
|
||||
template<int ANumDimensions, typename ACoordType>
|
||||
class Tree
|
||||
{
|
||||
public:
|
||||
static constexpr int NumDimensions = ANumDimensions;
|
||||
using CoordType = ACoordType;
|
||||
using VectorType = Eigen::Matrix<CoordType, NumDimensions, 1, Eigen::DontAlign>;
|
||||
using BoundingBox = Eigen::AlignedBox<CoordType, NumDimensions>;
|
||||
// Following could be static constexpr size_t, but that would not link in C++11
|
||||
enum : size_t {
|
||||
// Node is not used.
|
||||
npos = size_t(-1),
|
||||
// Inner node (not leaf).
|
||||
inner = size_t(-2)
|
||||
};
|
||||
|
||||
// Single node of the implicit balanced AABB tree. There are no links to the children nodes,
|
||||
// as these links are calculated implicitely using a power of two rule.
|
||||
struct Node {
|
||||
// Index of the external source entity, for which this AABB tree was built, npos for internal nodes.
|
||||
size_t idx = npos;
|
||||
// Bounding box around this entity, possibly with epsilons applied to fight numeric rounding errors
|
||||
// when traversing the AABB tree.
|
||||
BoundingBox bbox;
|
||||
|
||||
bool is_valid() const { return this->idx != npos; }
|
||||
bool is_inner() const { return this->idx == inner; }
|
||||
bool is_leaf() const { return ! this->is_inner(); }
|
||||
|
||||
template<typename SourceNode>
|
||||
void set(const SourceNode &rhs) {
|
||||
this->idx = rhs.idx();
|
||||
this->bbox = rhs.bbox();
|
||||
}
|
||||
};
|
||||
|
||||
void clear() { m_nodes.clear(); }
|
||||
|
||||
// SourceNode shall implement
|
||||
// size_t SourceNode::idx() const
|
||||
// - Index to the outside entity (triangle, edge, point etc).
|
||||
// const VectorType& SourceNode::centroid() const
|
||||
// - Centroid of this node. The centroid is used for balancing the tree.
|
||||
// const BoundingBox& SourceNode::bbox() const
|
||||
// - Bounding box of this node, likely expanded with epsilon to account for numeric rounding during tree traversal.
|
||||
// Union of bounding boxes at a single level of the AABB tree is used for deciding the longest axis aligned dimension
|
||||
// to split around.
|
||||
template<typename SourceNode>
|
||||
void build(std::vector<SourceNode> &&input)
|
||||
{
|
||||
if (input.empty())
|
||||
clear();
|
||||
else {
|
||||
// Allocate enough memory for a full binary tree.
|
||||
m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node());
|
||||
build_recursive(input, 0, 0, input.size() - 1);
|
||||
}
|
||||
input.clear();
|
||||
}
|
||||
|
||||
const std::vector<Node>& nodes() const { return m_nodes; }
|
||||
const Node& node(size_t idx) const { return m_nodes[idx]; }
|
||||
bool empty() const { return m_nodes.empty(); }
|
||||
|
||||
// Addressing the child nodes using the power of two rule.
|
||||
static size_t left_child_idx(size_t idx) { return idx * 2 + 1; }
|
||||
static size_t right_child_idx(size_t idx) { return left_child_idx(idx) + 1; }
|
||||
const Node& left_child(size_t idx) const { return m_nodes[left_child_idx(idx)]; }
|
||||
const Node& right_child(size_t idx) const { return m_nodes[right_child_idx(idx)]; }
|
||||
|
||||
template<typename SourceNode>
|
||||
void build(const std::vector<SourceNode> &input)
|
||||
{
|
||||
std::vector<SourceNode> copy(input);
|
||||
this->build(std::move(copy));
|
||||
}
|
||||
|
||||
private:
|
||||
// Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension.
|
||||
template<typename SourceNode>
|
||||
void build_recursive(std::vector<SourceNode> &input, size_t node, const size_t left, const size_t right)
|
||||
{
|
||||
assert(node < m_nodes.size());
|
||||
assert(left <= right);
|
||||
|
||||
if (left == right) {
|
||||
// Insert a node into the balanced tree.
|
||||
m_nodes[node].set(input[left]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate bounding box of the input.
|
||||
BoundingBox bbox(input[left].bbox());
|
||||
for (size_t i = left + 1; i <= right; ++ i)
|
||||
bbox.extend(input[i].bbox());
|
||||
int dimension = -1;
|
||||
bbox.diagonal().maxCoeff(&dimension);
|
||||
|
||||
// Partition the input to left / right pieces of the same length to produce a balanced tree.
|
||||
size_t center = (left + right) / 2;
|
||||
partition_input(input, size_t(dimension), left, right, center);
|
||||
// Insert an inner node into the tree. Inner node does not reference any input entity (triangle, line segment etc).
|
||||
m_nodes[node].idx = inner;
|
||||
m_nodes[node].bbox = bbox;
|
||||
build_recursive(input, node * 2 + 1, left, center);
|
||||
build_recursive(input, node * 2 + 2, center + 1, right);
|
||||
}
|
||||
|
||||
// Partition the input m_nodes <left, right> at "k" and "dimension" using the QuickSelect method:
|
||||
// https://en.wikipedia.org/wiki/Quickselect
|
||||
// Items left of the k'th item are lower than the k'th item in the "dimension",
|
||||
// items right of the k'th item are higher than the k'th item in the "dimension",
|
||||
template<typename SourceNode>
|
||||
void partition_input(std::vector<SourceNode> &input, const size_t dimension, size_t left, size_t right, const size_t k) const
|
||||
{
|
||||
while (left < right) {
|
||||
size_t center = (left + right) / 2;
|
||||
CoordType pivot;
|
||||
{
|
||||
// Bubble sort the input[left], input[center], input[right], so that a median of the three values
|
||||
// will end up in input[center].
|
||||
CoordType left_value = input[left ].centroid()(dimension);
|
||||
CoordType center_value = input[center].centroid()(dimension);
|
||||
CoordType right_value = input[right ].centroid()(dimension);
|
||||
if (left_value > center_value) {
|
||||
std::swap(input[left], input[center]);
|
||||
std::swap(left_value, center_value);
|
||||
}
|
||||
if (left_value > right_value) {
|
||||
std::swap(input[left], input[right]);
|
||||
right_value = left_value;
|
||||
}
|
||||
if (center_value > right_value) {
|
||||
std::swap(input[center], input[right]);
|
||||
center_value = right_value;
|
||||
}
|
||||
pivot = center_value;
|
||||
}
|
||||
if (right <= left + 2)
|
||||
// The <left, right> interval is already sorted.
|
||||
break;
|
||||
size_t i = left;
|
||||
size_t j = right - 1;
|
||||
std::swap(input[center], input[j]);
|
||||
// Partition the set based on the pivot.
|
||||
for (;;) {
|
||||
// Skip left points that are already at correct positions.
|
||||
// Search will certainly stop at position (right - 1), which stores the pivot.
|
||||
while (input[++ i].centroid()(dimension) < pivot) ;
|
||||
// Skip right points that are already at correct positions.
|
||||
while (input[-- j].centroid()(dimension) > pivot && i < j) ;
|
||||
if (i >= j)
|
||||
break;
|
||||
std::swap(input[i], input[j]);
|
||||
}
|
||||
// Restore pivot to the center of the sequence.
|
||||
std::swap(input[i], input[right - 1]);
|
||||
// Which side the kth element is in?
|
||||
if (k < i)
|
||||
right = i - 1;
|
||||
else if (k == i)
|
||||
// Sequence is partitioned, kth element is at its place.
|
||||
break;
|
||||
else
|
||||
left = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The balanced tree storage.
|
||||
std::vector<Node> m_nodes;
|
||||
};
|
||||
|
||||
using Tree2f = Tree<2, float>;
|
||||
using Tree3f = Tree<3, float>;
|
||||
using Tree2d = Tree<2, double>;
|
||||
using Tree3d = Tree<3, double>;
|
||||
|
||||
namespace detail {
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct RayIntersector {
|
||||
using VertexType = AVertexType;
|
||||
using IndexedFaceType = AIndexedFaceType;
|
||||
using TreeType = ATreeType;
|
||||
using VectorType = AVectorType;
|
||||
|
||||
const std::vector<VertexType> &vertices;
|
||||
const std::vector<IndexedFaceType> &faces;
|
||||
const TreeType &tree;
|
||||
|
||||
const VectorType origin;
|
||||
const VectorType dir;
|
||||
const VectorType invdir;
|
||||
};
|
||||
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
struct RayIntersectorHits : RayIntersector<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
std::vector<igl::Hit> hits;
|
||||
};
|
||||
|
||||
//FIXME implement SSE for float AABB trees with float ray queries.
|
||||
// SSE/SSE2 is supported by any Intel/AMD x64 processor.
|
||||
// SSE support requires 16 byte alignment of the AABB nodes, representing the bounding boxes with 4+4 floats,
|
||||
// storing the node index as the 4th element of the bounding box min value etc.
|
||||
// https://www.flipcode.com/archives/SSE_RayBox_Intersection_Test.shtml
|
||||
template <typename Derivedsource, typename Deriveddir, typename Scalar>
|
||||
inline bool ray_box_intersect_invdir(
|
||||
const Eigen::MatrixBase<Derivedsource> &origin,
|
||||
const Eigen::MatrixBase<Deriveddir> &inv_dir,
|
||||
Eigen::AlignedBox<Scalar,3> box,
|
||||
const Scalar &t0,
|
||||
const Scalar &t1) {
|
||||
// http://people.csail.mit.edu/amy/papers/box-jgt.pdf
|
||||
// "An Efficient and Robust Ray–Box Intersection Algorithm"
|
||||
if (inv_dir.x() < 0)
|
||||
std::swap(box.min().x(), box.max().x());
|
||||
if (inv_dir.y() < 0)
|
||||
std::swap(box.min().y(), box.max().y());
|
||||
Scalar tmin = (box.min().x() - origin.x()) * inv_dir.x();
|
||||
Scalar tymax = (box.max().y() - origin.y()) * inv_dir.y();
|
||||
if (tmin > tymax)
|
||||
return false;
|
||||
Scalar tmax = (box.max().x() - origin.x()) * inv_dir.x();
|
||||
Scalar tymin = (box.min().y() - origin.y()) * inv_dir.y();
|
||||
if (tymin > tmax)
|
||||
return false;
|
||||
if (tymin > tmin)
|
||||
tmin = tymin;
|
||||
if (tymax < tmax)
|
||||
tmax = tymax;
|
||||
if (inv_dir.z() < 0)
|
||||
std::swap(box.min().z(), box.max().z());
|
||||
Scalar tzmin = (box.min().z() - origin.z()) * inv_dir.z();
|
||||
if (tzmin > tmax)
|
||||
return false;
|
||||
Scalar tzmax = (box.max().z() - origin.z()) * inv_dir.z();
|
||||
if (tmin > tzmax)
|
||||
return false;
|
||||
if (tzmin > tmin)
|
||||
tmin = tzmin;
|
||||
if (tzmax < tmax)
|
||||
tmax = tzmax;
|
||||
return tmin < t1 && tmax > t0;
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
|
||||
return intersect_triangle1(const_cast<double*>(origin.data()), const_cast<double*>(dir.data()),
|
||||
const_cast<double*>(v0.data()), const_cast<double*>(v1.data()), const_cast<double*>(v2.data()),
|
||||
&t, &u, &v);
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && !std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
|
||||
using Vector = Eigen::Matrix<double, 3, 1>;
|
||||
Vector w0 = v0.template cast<double>();
|
||||
Vector w1 = v1.template cast<double>();
|
||||
Vector w2 = v2.template cast<double>();
|
||||
return intersect_triangle1(const_cast<double*>(origin.data()), const_cast<double*>(dir.data()),
|
||||
w0.data(), w1.data(), w2.data(), &t, &u, &v);
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
|
||||
using Vector = Eigen::Matrix<double, 3, 1>;
|
||||
Vector o = origin.template cast<double>();
|
||||
Vector d = dir.template cast<double>();
|
||||
return intersect_triangle1(o.data(), d.data(), const_cast<double*>(v0.data()), const_cast<double*>(v1.data()), const_cast<double*>(v2.data()), &t, &u, &v);
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && ! std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
|
||||
using Vector = Eigen::Matrix<double, 3, 1>;
|
||||
Vector o = origin.template cast<double>();
|
||||
Vector d = dir.template cast<double>();
|
||||
Vector w0 = v0.template cast<double>();
|
||||
Vector w1 = v1.template cast<double>();
|
||||
Vector w2 = v2.template cast<double>();
|
||||
return intersect_triangle1(o.data(), d.data(), w0.data(), w1.data(), w2.data(), &t, &u, &v);
|
||||
}
|
||||
|
||||
template<typename RayIntersectorType, typename Scalar>
|
||||
static inline bool intersect_ray_recursive_first_hit(
|
||||
RayIntersectorType &ray_intersector,
|
||||
size_t node_idx,
|
||||
Scalar min_t,
|
||||
igl::Hit &hit)
|
||||
{
|
||||
const auto &node = ray_intersector.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
|
||||
if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast<Scalar>(), Scalar(0), min_t))
|
||||
return false;
|
||||
|
||||
if (node.is_leaf()) {
|
||||
// shoot ray, record hit
|
||||
auto face = ray_intersector.faces[node.idx];
|
||||
double t, u, v;
|
||||
if (intersect_triangle(
|
||||
ray_intersector.origin, ray_intersector.dir,
|
||||
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
|
||||
t, u, v)
|
||||
&& t > 0.) {
|
||||
hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) };
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
} else {
|
||||
// Left / right child node index.
|
||||
size_t left = node_idx * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
igl::Hit left_hit;
|
||||
igl::Hit right_hit;
|
||||
bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit);
|
||||
if (left_ret && left_hit.t < min_t) {
|
||||
min_t = left_hit.t;
|
||||
hit = left_hit;
|
||||
} else
|
||||
left_ret = false;
|
||||
bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit);
|
||||
if (right_ret && right_hit.t < min_t)
|
||||
hit = right_hit;
|
||||
else
|
||||
right_ret = false;
|
||||
return left_ret || right_ret;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename RayIntersectorType>
|
||||
static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx)
|
||||
{
|
||||
using Scalar = typename RayIntersectorType::VectorType::Scalar;
|
||||
|
||||
const auto &node = ray_intersector.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
|
||||
if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast<Scalar>(),
|
||||
Scalar(0), std::numeric_limits<Scalar>::infinity()))
|
||||
return;
|
||||
|
||||
if (node.is_leaf()) {
|
||||
auto face = ray_intersector.faces[node.idx];
|
||||
double t, u, v;
|
||||
if (intersect_triangle(
|
||||
ray_intersector.origin, ray_intersector.dir,
|
||||
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
|
||||
t, u, v)
|
||||
&& t > 0.) {
|
||||
ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) });
|
||||
}
|
||||
} else {
|
||||
// Left / right child node index.
|
||||
size_t left = node_idx * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
intersect_ray_recursive_all_hits(ray_intersector, left);
|
||||
intersect_ray_recursive_all_hits(ray_intersector, right);
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing to do with COVID-19 social distancing.
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct IndexedTriangleSetDistancer {
|
||||
using VertexType = AVertexType;
|
||||
using IndexedFaceType = AIndexedFaceType;
|
||||
using TreeType = ATreeType;
|
||||
using VectorType = AVectorType;
|
||||
|
||||
const std::vector<VertexType> &vertices;
|
||||
const std::vector<IndexedFaceType> &faces;
|
||||
const TreeType &tree;
|
||||
|
||||
const VectorType origin;
|
||||
};
|
||||
|
||||
// Real-time collision detection, Ericson, Chapter 5
|
||||
template<typename Vector>
|
||||
static inline Vector closest_point_to_triangle(const Vector &p, const Vector &a, const Vector &b, const Vector &c)
|
||||
{
|
||||
using Scalar = typename Vector::Scalar;
|
||||
// Check if P in vertex region outside A
|
||||
Vector ab = b - a;
|
||||
Vector ac = c - a;
|
||||
Vector ap = p - a;
|
||||
Scalar d1 = ab.dot(ap);
|
||||
Scalar d2 = ac.dot(ap);
|
||||
if (d1 <= 0 && d2 <= 0)
|
||||
return a;
|
||||
// Check if P in vertex region outside B
|
||||
Vector bp = p - b;
|
||||
Scalar d3 = ab.dot(bp);
|
||||
Scalar d4 = ac.dot(bp);
|
||||
if (d3 >= 0 && d4 <= d3)
|
||||
return b;
|
||||
// Check if P in edge region of AB, if so return projection of P onto AB
|
||||
Scalar vc = d1*d4 - d3*d2;
|
||||
if (a != b && vc <= 0 && d1 >= 0 && d3 <= 0) {
|
||||
Scalar v = d1 / (d1 - d3);
|
||||
return a + v * ab;
|
||||
}
|
||||
// Check if P in vertex region outside C
|
||||
Vector cp = p - c;
|
||||
Scalar d5 = ab.dot(cp);
|
||||
Scalar d6 = ac.dot(cp);
|
||||
if (d6 >= 0 && d5 <= d6)
|
||||
return c;
|
||||
// Check if P in edge region of AC, if so return projection of P onto AC
|
||||
Scalar vb = d5*d2 - d1*d6;
|
||||
if (vb <= 0 && d2 >= 0 && d6 <= 0) {
|
||||
Scalar w = d2 / (d2 - d6);
|
||||
return a + w * ac;
|
||||
}
|
||||
// Check if P in edge region of BC, if so return projection of P onto BC
|
||||
Scalar va = d3*d6 - d5*d4;
|
||||
if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) {
|
||||
Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
|
||||
return b + w * (c - b);
|
||||
}
|
||||
// P inside face region. Compute Q through its barycentric coordinates (u,v,w)
|
||||
Scalar denom = Scalar(1.0) / (va + vb + vc);
|
||||
Scalar v = vb * denom;
|
||||
Scalar w = vc * denom;
|
||||
return a + ab * v + ac * w; // = u*a + v*b + w*c, u = va * denom = 1.0-v-w
|
||||
};
|
||||
|
||||
template<typename IndexedTriangleSetDistancerType, typename Scalar>
|
||||
static inline Scalar squared_distance_to_indexed_triangle_set_recursive(
|
||||
IndexedTriangleSetDistancerType &distancer,
|
||||
size_t node_idx,
|
||||
Scalar low_sqr_d,
|
||||
Scalar up_sqr_d,
|
||||
size_t &i,
|
||||
Eigen::PlainObjectBase<typename IndexedTriangleSetDistancerType::VectorType> &c)
|
||||
{
|
||||
using Vector = typename IndexedTriangleSetDistancerType::VectorType;
|
||||
|
||||
if (low_sqr_d > up_sqr_d)
|
||||
return low_sqr_d;
|
||||
|
||||
// Save the best achieved hit.
|
||||
auto set_min = [&i, &c, &up_sqr_d](const Scalar sqr_d_candidate, const size_t i_candidate, const Vector &c_candidate) {
|
||||
if (sqr_d_candidate < up_sqr_d) {
|
||||
i = i_candidate;
|
||||
c = c_candidate;
|
||||
up_sqr_d = sqr_d_candidate;
|
||||
}
|
||||
};
|
||||
|
||||
const auto &node = distancer.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf())
|
||||
{
|
||||
const auto &triangle = distancer.faces[node.idx];
|
||||
Vector c_candidate = closest_point_to_triangle<Vector>(
|
||||
distancer.origin,
|
||||
distancer.vertices[triangle(0)].template cast<Scalar>(),
|
||||
distancer.vertices[triangle(1)].template cast<Scalar>(),
|
||||
distancer.vertices[triangle(2)].template cast<Scalar>());
|
||||
set_min((c_candidate - distancer.origin).squaredNorm(), node.idx, c_candidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto &node_left = distancer.tree.node(left_node_idx);
|
||||
const auto &node_right = distancer.tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
bool looked_left = false;
|
||||
bool looked_right = false;
|
||||
const auto &look_left = [&]()
|
||||
{
|
||||
size_t i_left;
|
||||
Vector c_left = c;
|
||||
Scalar sqr_d_left = squared_distance_to_indexed_triangle_set_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left);
|
||||
set_min(sqr_d_left, i_left, c_left);
|
||||
looked_left = true;
|
||||
};
|
||||
const auto &look_right = [&]()
|
||||
{
|
||||
size_t i_right;
|
||||
Vector c_right = c;
|
||||
Scalar sqr_d_right = squared_distance_to_indexed_triangle_set_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right);
|
||||
set_min(sqr_d_right, i_right, c_right);
|
||||
looked_right = true;
|
||||
};
|
||||
|
||||
// must look left or right if in box
|
||||
using BBoxScalar = typename IndexedTriangleSetDistancerType::TreeType::BoundingBox::Scalar;
|
||||
if (node_left.bbox.contains(distancer.origin.template cast<BBoxScalar>()))
|
||||
look_left();
|
||||
if (node_right.bbox.contains(distancer.origin.template cast<BBoxScalar>()))
|
||||
look_right();
|
||||
// if haven't looked left and could be less than current min, then look
|
||||
Scalar left_up_sqr_d = node_left.bbox.squaredExteriorDistance(distancer.origin);
|
||||
Scalar right_up_sqr_d = node_right.bbox.squaredExteriorDistance(distancer.origin);
|
||||
if (left_up_sqr_d < right_up_sqr_d) {
|
||||
if (! looked_left && left_up_sqr_d < up_sqr_d)
|
||||
look_left();
|
||||
if (! looked_right && right_up_sqr_d < up_sqr_d)
|
||||
look_right();
|
||||
} else {
|
||||
if (! looked_right && right_up_sqr_d < up_sqr_d)
|
||||
look_right();
|
||||
if (! looked_left && left_up_sqr_d < up_sqr_d)
|
||||
look_left();
|
||||
}
|
||||
}
|
||||
return up_sqr_d;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Build a balanced AABB Tree over an indexed triangles set, balancing the tree
|
||||
// on centroids of the triangles.
|
||||
// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies
|
||||
// during tree traversal.
|
||||
template<typename VertexType, typename IndexedFaceType>
|
||||
inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangle_set(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
//FIXME do we want to apply an epsilon?
|
||||
const typename VertexType::Scalar eps = 0)
|
||||
{
|
||||
using TreeType = Tree<3, typename VertexType::Scalar>;
|
||||
// using CoordType = typename TreeType::CoordType;
|
||||
using VectorType = typename TreeType::VectorType;
|
||||
using BoundingBox = typename TreeType::BoundingBox;
|
||||
|
||||
struct InputType {
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
const VectorType& centroid() const { return m_centroid; }
|
||||
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
VectorType m_centroid;
|
||||
};
|
||||
|
||||
std::vector<InputType> input;
|
||||
input.reserve(faces.size());
|
||||
const VectorType veps(eps, eps, eps);
|
||||
for (size_t i = 0; i < faces.size(); ++ i) {
|
||||
const IndexedFaceType &face = faces[i];
|
||||
const VertexType &v1 = vertices[face(0)];
|
||||
const VertexType &v2 = vertices[face(1)];
|
||||
const VertexType &v3 = vertices[face(2)];
|
||||
InputType n;
|
||||
n.m_idx = i;
|
||||
n.m_centroid = (1./3.) * (v1 + v2 + v3);
|
||||
n.m_bbox = BoundingBox(v1, v1);
|
||||
n.m_bbox.extend(v2);
|
||||
n.m_bbox.extend(v3);
|
||||
n.m_bbox.min() -= veps;
|
||||
n.m_bbox.max() += veps;
|
||||
input.emplace_back(n);
|
||||
}
|
||||
|
||||
TreeType out;
|
||||
out.build(std::move(input));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Find a first intersection of a ray with indexed triangle set.
|
||||
// Intersection test is calculated with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline bool intersect_ray_first_hit(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Origin of the ray.
|
||||
const VectorType &origin,
|
||||
// Direction of the ray.
|
||||
const VectorType &dir,
|
||||
// First intersection of the ray with the indexed triangle set.
|
||||
igl::Hit &hit)
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
auto ray_intersector = detail::RayIntersector<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
vertices, faces, tree,
|
||||
origin, dir, VectorType(dir.cwiseInverse())
|
||||
};
|
||||
return ! tree.empty() && detail::intersect_ray_recursive_first_hit(
|
||||
ray_intersector, size_t(0), std::numeric_limits<Scalar>::infinity(), hit);
|
||||
}
|
||||
|
||||
// Find all intersections of a ray with indexed triangle set.
|
||||
// Intersection test is calculated with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// The output hits are sorted by the ray parameter.
|
||||
// If the ray intersects a shared edge of two triangles, hits for both triangles are returned.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline bool intersect_ray_all_hits(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Origin of the ray.
|
||||
const VectorType &origin,
|
||||
// Direction of the ray.
|
||||
const VectorType &dir,
|
||||
// All intersections of the ray with the indexed triangle set, sorted by parameter t.
|
||||
std::vector<igl::Hit> &hits)
|
||||
{
|
||||
auto ray_intersector = detail::RayIntersectorHits<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
vertices, faces, tree,
|
||||
origin, dir, VectorType(dir.cwiseInverse())
|
||||
};
|
||||
if (! tree.empty()) {
|
||||
ray_intersector.hits.reserve(8);
|
||||
detail::intersect_ray_recursive_all_hits(ray_intersector, 0);
|
||||
std::swap(hits, ray_intersector.hits);
|
||||
std::sort(hits.begin(), hits.end(), [](const auto &l, const auto &r) { return l.t < r.t; });
|
||||
}
|
||||
return ! hits.empty();
|
||||
}
|
||||
|
||||
// Finding a closest triangle, its closest point and squared distance to the closest point
|
||||
// on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree.
|
||||
// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// Returns squared distance to the closest point or -1 if the input is empty.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Point to which the closest point on the indexed triangle set is searched for.
|
||||
const VectorType &point,
|
||||
// Index of the closest triangle in faces.
|
||||
size_t &hit_idx_out,
|
||||
// Position of the closest point on the indexed triangle set.
|
||||
Eigen::PlainObjectBase<VectorType> &hit_point_out)
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
|
||||
{ vertices, faces, tree, point };
|
||||
return tree.empty() ? Scalar(-1) :
|
||||
detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits<Scalar>::infinity(), hit_idx_out, hit_point_out);
|
||||
}
|
||||
|
||||
} // namespace AABBTreeIndirect
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_AABBTreeIndirect_hpp_ */
|
@ -51,8 +51,8 @@ template<class S> struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
|
||||
namespace Slic3r {
|
||||
|
||||
template<class Tout = double, class = FloatingOnly<Tout>, int...EigenArgs>
|
||||
inline SLIC3R_CONSTEXPR Eigen::Matrix<Tout, 2, EigenArgs...> unscaled(
|
||||
const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT
|
||||
inline constexpr Eigen::Matrix<Tout, 2, EigenArgs...> unscaled(
|
||||
const ClipperLib::IntPoint &v) noexcept
|
||||
{
|
||||
return Eigen::Matrix<Tout, 2, EigenArgs...>{unscaled<Tout>(v.X),
|
||||
unscaled<Tout>(v.Y)};
|
||||
|
@ -306,7 +306,7 @@ if(WIN32)
|
||||
endif()
|
||||
|
||||
if(SLIC3R_PROFILE)
|
||||
target_link_libraries(slic3r Shiny)
|
||||
target_link_libraries(libslic3r Shiny)
|
||||
endif()
|
||||
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
|
@ -369,7 +369,7 @@ namespace boost { namespace polygon {
|
||||
typedef coord_t coordinate_type;
|
||||
|
||||
static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) {
|
||||
return (orient == HORIZONTAL) ? (coordinate_type)point(0) : (coordinate_type)point(1);
|
||||
return (coordinate_type)point((orient == HORIZONTAL) ? 0 : 1);
|
||||
}
|
||||
};
|
||||
|
||||
@ -377,16 +377,10 @@ namespace boost { namespace polygon {
|
||||
struct point_mutable_traits<Slic3r::Point> {
|
||||
typedef coord_t coordinate_type;
|
||||
static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) {
|
||||
if (orient == HORIZONTAL)
|
||||
point(0) = value;
|
||||
else
|
||||
point(1) = value;
|
||||
point((orient == HORIZONTAL) ? 0 : 1) = value;
|
||||
}
|
||||
static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) {
|
||||
Slic3r::Point retval;
|
||||
retval(0) = x_value;
|
||||
retval(1) = y_value;
|
||||
return retval;
|
||||
return Slic3r::Point(x_value, y_value);
|
||||
}
|
||||
};
|
||||
} }
|
||||
|
@ -24,37 +24,37 @@
|
||||
|
||||
#if 1
|
||||
// Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final).
|
||||
typedef int32_t coord_t;
|
||||
using coord_t = int32_t;
|
||||
#else
|
||||
//FIXME At least FillRectilinear2 requires coord_t to be 32bit.
|
||||
typedef int64_t coord_t;
|
||||
#endif
|
||||
|
||||
typedef double coordf_t;
|
||||
using coordf_t = double;
|
||||
|
||||
//FIXME This epsilon value is used for many non-related purposes:
|
||||
// For a threshold of a squared Euclidean distance,
|
||||
// for a trheshold in a difference of radians,
|
||||
// for a threshold of a cross product of two non-normalized vectors etc.
|
||||
#define EPSILON 1e-4
|
||||
static constexpr double EPSILON = 1e-4;
|
||||
// Scaling factor for a conversion from coord_t to coordf_t: 10e-6
|
||||
// This scaling generates a following fixed point representation with for a 32bit integer:
|
||||
// 0..4294mm with 1nm resolution
|
||||
// int32_t fits an interval of (-2147.48mm, +2147.48mm)
|
||||
// with int64_t we don't have to worry anymore about the size of the int.
|
||||
#define SCALING_FACTOR 0.000001
|
||||
static constexpr double SCALING_FACTOR = 0.000001;
|
||||
// RESOLUTION, SCALED_RESOLUTION: Used as an error threshold for a Douglas-Peucker polyline simplification algorithm.
|
||||
#define RESOLUTION 0.0125
|
||||
#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR)
|
||||
#define PI 3.141592653589793238
|
||||
static constexpr double RESOLUTION = 0.0125;
|
||||
#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR)
|
||||
static constexpr double PI = 3.141592653589793238;
|
||||
// When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam.
|
||||
#define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15
|
||||
static constexpr double LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER = 0.15;
|
||||
// Maximum perimeter length for the loop to apply the small perimeter speed.
|
||||
#define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI
|
||||
#define INSET_OVERLAP_TOLERANCE 0.4
|
||||
#define SMALL_PERIMETER_LENGTH ((6.5 / SCALING_FACTOR) * 2 * PI)
|
||||
static constexpr double INSET_OVERLAP_TOLERANCE = 0.4;
|
||||
// 3mm ring around the top / bottom / bridging areas.
|
||||
//FIXME This is quite a lot.
|
||||
#define EXTERNAL_INFILL_MARGIN 3.
|
||||
static constexpr double EXTERNAL_INFILL_MARGIN = 3.;
|
||||
//FIXME Better to use an inline function with an explicit return type.
|
||||
//inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); }
|
||||
#define scale_(val) ((val) / SCALING_FACTOR)
|
||||
@ -63,14 +63,6 @@ typedef double coordf_t;
|
||||
|
||||
#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/"
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
# define SLIC3R_CONSTEXPR
|
||||
# define SLIC3R_NOEXCEPT
|
||||
#else
|
||||
#define SLIC3R_CONSTEXPR constexpr
|
||||
#define SLIC3R_NOEXCEPT noexcept
|
||||
#endif
|
||||
|
||||
inline std::string debug_out_path(const char *name, ...)
|
||||
{
|
||||
char buffer[2048];
|
||||
@ -92,11 +84,6 @@ inline std::string debug_out_path(const char *name, ...)
|
||||
#define UNUSED(x) (void)(x)
|
||||
#endif /* UNUSED */
|
||||
|
||||
// Detect whether the compiler supports C++11 noexcept exception specifications.
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define noexcept throw()
|
||||
#endif
|
||||
|
||||
// Write slices as SVG images into out directory during the 2D processing of the slices.
|
||||
// #define SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
|
||||
|
@ -310,7 +310,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
toggle_field("standby_temperature_delta", have_ooze_prevention);
|
||||
|
||||
bool have_wipe_tower = config->opt_bool("wipe_tower");
|
||||
for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging" })
|
||||
for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle",
|
||||
"wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
|
||||
toggle_field(el, have_wipe_tower);
|
||||
}
|
||||
|
||||
|
@ -4029,6 +4029,26 @@ void ObjectList::msw_rescale()
|
||||
Layout();
|
||||
}
|
||||
|
||||
void ObjectList::sys_color_changed()
|
||||
{
|
||||
// msw_rescale_icons() updates icons, so use it
|
||||
msw_rescale_icons();
|
||||
|
||||
// update existing items with bitmaps
|
||||
m_objects_model->Rescale();
|
||||
|
||||
// msw_rescale_menu updates just icons, so use it
|
||||
for (MenuWithSeparators* menu : { &m_menu_object,
|
||||
&m_menu_part,
|
||||
&m_menu_sla_object,
|
||||
&m_menu_instance,
|
||||
&m_menu_layer,
|
||||
&m_menu_default})
|
||||
msw_rescale_menu(menu);
|
||||
|
||||
Layout();
|
||||
}
|
||||
|
||||
void ObjectList::ItemValueChanged(wxDataViewEvent &event)
|
||||
{
|
||||
if (event.GetColumn() == colName)
|
||||
|
@ -386,6 +386,7 @@ public:
|
||||
void paste_objects_into_list(const std::vector<size_t>& object_idxs);
|
||||
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
|
||||
void update_after_undo_redo();
|
||||
//update printable state for item from objects model
|
||||
|
@ -981,6 +981,23 @@ void ObjectManipulation::msw_rescale()
|
||||
get_og()->msw_rescale();
|
||||
}
|
||||
|
||||
void ObjectManipulation::sys_color_changed()
|
||||
{
|
||||
// btn...->msw_rescale() updates icon on button, so use it
|
||||
m_mirror_bitmap_on.msw_rescale();
|
||||
m_mirror_bitmap_off.msw_rescale();
|
||||
m_mirror_bitmap_hidden.msw_rescale();
|
||||
m_reset_scale_button->msw_rescale();
|
||||
m_reset_rotation_button->msw_rescale();
|
||||
m_drop_to_bed_button->msw_rescale();
|
||||
m_lock_bnt->msw_rescale();
|
||||
|
||||
for (int id = 0; id < 3; ++id)
|
||||
m_mirror_buttons[id].first->msw_rescale();
|
||||
|
||||
get_og()->msw_rescale();
|
||||
}
|
||||
|
||||
static const char axes[] = { 'x', 'y', 'z' };
|
||||
ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
|
||||
const std::string& opt_key,
|
||||
|
@ -173,6 +173,7 @@ public:
|
||||
void update_item_name(const wxString &item_name);
|
||||
void update_warning_icon_state(const wxString& tooltip);
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
void on_change(const std::string& opt_key, int axis, double new_value);
|
||||
void set_focused_editor(ManipulationEditor* focused_editor) {
|
||||
#ifndef __APPLE__
|
||||
|
@ -124,6 +124,12 @@ public:
|
||||
// set value to _true_ in purpose of possibility of a display dpi changing from System Settings
|
||||
m_can_rescale = true;
|
||||
});
|
||||
|
||||
this->Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
on_sys_color_changed();
|
||||
});
|
||||
}
|
||||
|
||||
virtual ~DPIAware() {}
|
||||
@ -137,6 +143,7 @@ public:
|
||||
|
||||
protected:
|
||||
virtual void on_dpi_changed(const wxRect &suggested_rect) = 0;
|
||||
virtual void on_sys_color_changed() {};
|
||||
|
||||
private:
|
||||
float m_scale_factor;
|
||||
|
@ -805,14 +805,6 @@ static const ImWchar ranges_keyboard_shortcuts[] =
|
||||
|
||||
std::vector<unsigned char> ImGuiWrapper::load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
// Note: win->GetContentScaleFactor() is not used anymore here because it tends to
|
||||
// return bogus results quite often (such as 1.0 on Retina or even 0.0).
|
||||
// We're using the max scaling factor across all screens because it's very likely to be good enough.
|
||||
double scale = mac_max_scaling_factor();
|
||||
#else
|
||||
double scale = 1.0;
|
||||
#endif
|
||||
std::vector<unsigned char> empty_vector;
|
||||
|
||||
#ifdef __WXMSW__
|
||||
@ -827,8 +819,6 @@ std::vector<unsigned char> ImGuiWrapper::load_svg(const std::string& bitmap_name
|
||||
if (image == nullptr)
|
||||
return empty_vector;
|
||||
|
||||
target_height != 0 ? target_height *= scale : target_width *= scale;
|
||||
|
||||
float svg_scale = target_height != 0 ?
|
||||
(float)target_height / image->height : target_width != 0 ?
|
||||
(float)target_width / image->width : 1;
|
||||
|
@ -190,6 +190,29 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||
event.Skip();
|
||||
});
|
||||
|
||||
/*
|
||||
Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event)
|
||||
{
|
||||
bool recreate_gui = false;
|
||||
{
|
||||
// the dialog needs to be destroyed before the call to recreate_gui()
|
||||
// or sometimes the application crashes into wxDialogBase() destructor
|
||||
// so we put it into an inner scope
|
||||
wxMessageDialog dialog(nullptr,
|
||||
_L("System color mode was changed. "
|
||||
"It is possible to update the Slicer in respect to the system mode.") + "\n" +
|
||||
_L("You will lose content of the plater.") + "\n\n" +
|
||||
_L("Do you want to proceed?"),
|
||||
wxString(SLIC3R_APP_NAME) + " - " + _L("Switching system color mode"),
|
||||
wxICON_QUESTION | wxOK | wxCANCEL);
|
||||
recreate_gui = dialog.ShowModal() == wxID_OK;
|
||||
}
|
||||
if (recreate_gui)
|
||||
wxGetApp().recreate_GUI(_L("Changing of an application in respect to the system mode") + dots);
|
||||
event.Skip();
|
||||
});
|
||||
*/
|
||||
|
||||
wxGetApp().persist_window_geometry(this, true);
|
||||
|
||||
update_ui_from_settings(); // FIXME (?)
|
||||
@ -554,6 +577,28 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect)
|
||||
this->Maximize(is_maximized);
|
||||
}
|
||||
|
||||
void MainFrame::on_sys_color_changed()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
|
||||
// update label colors in respect to the system mode
|
||||
wxGetApp().init_label_colours();
|
||||
|
||||
wxGetApp().preset_bundle->load_default_preset_bitmaps();
|
||||
|
||||
// update Plater
|
||||
wxGetApp().plater()->sys_color_changed();
|
||||
|
||||
// update Tabs
|
||||
for (auto tab : wxGetApp().tabs_list)
|
||||
tab->sys_color_changed();
|
||||
|
||||
// msw_rescale_menu updates just icons, so use it
|
||||
wxMenuBar* menu_bar = this->GetMenuBar();
|
||||
for (size_t id = 0; id < menu_bar->GetMenuCount(); id++)
|
||||
msw_rescale_menu(menu_bar->GetMenu(id));
|
||||
}
|
||||
|
||||
void MainFrame::init_menubar()
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
|
@ -121,6 +121,7 @@ class MainFrame : public DPIFrame
|
||||
|
||||
protected:
|
||||
virtual void on_dpi_changed(const wxRect &suggested_rect);
|
||||
virtual void on_sys_color_changed() override;
|
||||
|
||||
public:
|
||||
MainFrame();
|
||||
|
@ -1092,6 +1092,34 @@ void Sidebar::msw_rescale()
|
||||
p->scrolled->Layout();
|
||||
}
|
||||
|
||||
void Sidebar::sys_color_changed()
|
||||
{
|
||||
// Update preset comboboxes in respect to the system color ...
|
||||
// combo->msw_rescale() updates icon on button, so use it
|
||||
for (PresetComboBox* combo : std::vector<PresetComboBox*>{ p->combo_print,
|
||||
p->combo_sla_print,
|
||||
p->combo_sla_material,
|
||||
p->combo_printer })
|
||||
combo->msw_rescale();
|
||||
for (PresetComboBox* combo : p->combos_filament)
|
||||
combo->msw_rescale();
|
||||
|
||||
// ... then refill them and set min size to correct layout of the sidebar
|
||||
update_all_preset_comboboxes();
|
||||
|
||||
p->object_list->sys_color_changed();
|
||||
p->object_manipulation->sys_color_changed();
|
||||
// p->object_settings->msw_rescale();
|
||||
// p->object_layers->msw_rescale();
|
||||
|
||||
// btn...->msw_rescale() updates icon on button, so use it
|
||||
p->btn_send_gcode->msw_rescale();
|
||||
p->btn_remove_device->msw_rescale();
|
||||
p->btn_export_gcode_removable->msw_rescale();
|
||||
|
||||
p->scrolled->Layout();
|
||||
}
|
||||
|
||||
void Sidebar::search()
|
||||
{
|
||||
p->searcher.search();
|
||||
@ -5514,6 +5542,17 @@ void Plater::msw_rescale()
|
||||
GetParent()->Layout();
|
||||
}
|
||||
|
||||
void Plater::sys_color_changed()
|
||||
{
|
||||
p->sidebar->sys_color_changed();
|
||||
|
||||
// msw_rescale_menu updates just icons, so use it
|
||||
p->msw_rescale_object_menu();
|
||||
|
||||
Layout();
|
||||
GetParent()->Layout();
|
||||
}
|
||||
|
||||
bool Plater::init_view_toolbar()
|
||||
{
|
||||
return p->init_view_toolbar();
|
||||
|
@ -105,6 +105,7 @@ public:
|
||||
void update_mode_sizer() const;
|
||||
void update_reslice_btn_tooltip() const;
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
void search();
|
||||
void jump_to_option(size_t selected);
|
||||
|
||||
@ -308,6 +309,7 @@ public:
|
||||
bool can_reload_from_disk() const;
|
||||
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
|
||||
bool init_view_toolbar();
|
||||
|
||||
|
@ -668,6 +668,14 @@ void SearchDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void SearchDialog::on_sys_color_changed()
|
||||
{
|
||||
// msw_rescale updates just icons, so use it
|
||||
search_list_model->msw_rescale();
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// SearchListModel
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -198,6 +198,7 @@ public:
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
virtual void on_sys_color_changed() override;
|
||||
};
|
||||
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <Eigen/Core>
|
||||
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/platinfo.h>
|
||||
#include "GUI_App.hpp"
|
||||
@ -145,11 +147,11 @@ SysInfoDialog::SysInfoDialog()
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>", bgr_clr_str, text_clr_str, text_clr_str,
|
||||
get_mem_info(true) + "<br>" + wxGetApp().get_gl_info(true, true));
|
||||
get_mem_info(true) + "<br>" + wxGetApp().get_gl_info(true, true) + "<br>Eigen vectorization supported: " + Eigen::SimdInstructionSetsInUse());
|
||||
m_opengl_info_html->SetPage(text);
|
||||
main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15);
|
||||
}
|
||||
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK);
|
||||
m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _(L("Copy to Clipboard")), wxDefaultPosition, wxDefaultSize);
|
||||
|
||||
|
@ -590,6 +590,18 @@ void TabPrinter::msw_rescale()
|
||||
Layout();
|
||||
}
|
||||
|
||||
void TabPrinter::sys_color_changed()
|
||||
{
|
||||
Tab::sys_color_changed();
|
||||
|
||||
// update missed options_groups
|
||||
const std::vector<PageShp>& pages = m_printer_technology == ptFFF ? m_pages_sla : m_pages_fff;
|
||||
for (auto page : pages)
|
||||
page->msw_rescale();
|
||||
|
||||
Layout();
|
||||
}
|
||||
|
||||
void TabSLAMaterial::init_options_list()
|
||||
{
|
||||
if (!m_options_list.empty())
|
||||
@ -869,6 +881,41 @@ void Tab::msw_rescale()
|
||||
Layout();
|
||||
}
|
||||
|
||||
void Tab::sys_color_changed()
|
||||
{
|
||||
update_tab_ui();
|
||||
|
||||
// update buttons and cached bitmaps
|
||||
for (const auto btn : m_scaled_buttons)
|
||||
btn->msw_rescale();
|
||||
for (const auto bmp : m_scaled_bitmaps)
|
||||
bmp->msw_rescale();
|
||||
for (ScalableBitmap& bmp : m_mode_bitmap_cache)
|
||||
bmp.msw_rescale();
|
||||
|
||||
// update icons for tree_ctrl
|
||||
for (ScalableBitmap& bmp : m_scaled_icons_list)
|
||||
bmp.msw_rescale();
|
||||
// recreate and set new ImageList for tree_ctrl
|
||||
m_icons->RemoveAll();
|
||||
m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight());
|
||||
for (ScalableBitmap& bmp : m_scaled_icons_list)
|
||||
m_icons->Add(bmp.bmp());
|
||||
m_treectrl->AssignImageList(m_icons);
|
||||
|
||||
|
||||
// Colors for ui "decoration"
|
||||
m_sys_label_clr = wxGetApp().get_label_clr_sys();
|
||||
m_modified_label_clr = wxGetApp().get_label_clr_modified();
|
||||
update_labels_colour();
|
||||
|
||||
// update options_groups
|
||||
for (auto page : m_pages)
|
||||
page->msw_rescale();
|
||||
|
||||
Layout();
|
||||
}
|
||||
|
||||
Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const
|
||||
{
|
||||
Field* field = nullptr;
|
||||
|
@ -318,6 +318,7 @@ public:
|
||||
void update_mode();
|
||||
void update_visibility();
|
||||
virtual void msw_rescale();
|
||||
virtual void sys_color_changed();
|
||||
Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const;
|
||||
Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1);
|
||||
bool set_value(const t_config_option_key& opt_key, const boost::any& value);
|
||||
@ -436,6 +437,7 @@ public:
|
||||
void on_preset_loaded() override;
|
||||
void init_options_list() override;
|
||||
void msw_rescale() override;
|
||||
void sys_color_changed() override;
|
||||
bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; }
|
||||
|
||||
wxSizer* create_bed_shape_widget(wxWindow* parent);
|
||||
|
@ -3,6 +3,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests.cpp
|
||||
test_3mf.cpp
|
||||
test_aabbindirect.cpp
|
||||
test_clipper_offset.cpp
|
||||
test_clipper_utils.cpp
|
||||
test_config.cpp
|
||||
|
61
tests/libslic3r/test_aabbindirect.cpp
Normal file
61
tests/libslic3r/test_aabbindirect.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndirect]")
|
||||
{
|
||||
TriangleMesh tmesh = make_cube(1., 1., 1.);
|
||||
tmesh.repair();
|
||||
|
||||
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(tmesh.its.vertices, tmesh.its.indices);
|
||||
REQUIRE(! tree.empty());
|
||||
|
||||
igl::Hit hit;
|
||||
bool intersected = AABBTreeIndirect::intersect_ray_first_hit(
|
||||
tmesh.its.vertices, tmesh.its.indices,
|
||||
tree,
|
||||
Vec3d(0.5, 0.5, -5.),
|
||||
Vec3d(0., 0., 1.),
|
||||
hit);
|
||||
|
||||
REQUIRE(intersected);
|
||||
REQUIRE(hit.t == Approx(5.));
|
||||
|
||||
std::vector<igl::Hit> hits;
|
||||
bool intersected2 = AABBTreeIndirect::intersect_ray_all_hits(
|
||||
tmesh.its.vertices, tmesh.its.indices,
|
||||
tree,
|
||||
Vec3d(0.3, 0.5, -5.),
|
||||
Vec3d(0., 0., 1.),
|
||||
hits);
|
||||
REQUIRE(intersected2);
|
||||
REQUIRE(hits.size() == 2);
|
||||
REQUIRE(hits.front().t == Approx(5.));
|
||||
REQUIRE(hits.back().t == Approx(6.));
|
||||
|
||||
size_t hit_idx;
|
||||
Vec3d closest_point;
|
||||
double squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
tmesh.its.vertices, tmesh.its.indices,
|
||||
tree,
|
||||
Vec3d(0.3, 0.5, -5.),
|
||||
hit_idx, closest_point);
|
||||
REQUIRE(squared_distance == Approx(5. * 5.));
|
||||
REQUIRE(closest_point.x() == Approx(0.3));
|
||||
REQUIRE(closest_point.y() == Approx(0.5));
|
||||
REQUIRE(closest_point.z() == Approx(0.));
|
||||
|
||||
squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
tmesh.its.vertices, tmesh.its.indices,
|
||||
tree,
|
||||
Vec3d(0.3, 0.5, 5.),
|
||||
hit_idx, closest_point);
|
||||
REQUIRE(squared_distance == Approx(4. * 4.));
|
||||
REQUIRE(closest_point.x() == Approx(0.3));
|
||||
REQUIRE(closest_point.y() == Approx(0.5));
|
||||
REQUIRE(closest_point.z() == Approx(1.));
|
||||
}
|
Loading…
Reference in New Issue
Block a user