mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-01-31 00:48:41 +03:00
update
This commit is contained in:
6
sandboxes/CMakeLists.txt
Normal file
6
sandboxes/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
#add_subdirectory(slasupporttree)
|
||||
#add_subdirectory(openvdb)
|
||||
# add_subdirectory(meshboolean)
|
||||
add_subdirectory(its_neighbor_index)
|
||||
# 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})
|
||||
223
sandboxes/aabb-evaluation/aabb-evaluation.cpp
Normal file
223
sandboxes/aabb-evaluation/aabb-evaluation.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
if (mesh.empty()) {
|
||||
std::cerr << "Error loading " << argv[1] << " . It is empty." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
profile(mesh);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
7
sandboxes/its_neighbor_index/CMakeLists.txt
Normal file
7
sandboxes/its_neighbor_index/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
add_executable(its_neighbor_index main.cpp ItsNeighborIndex.cpp ItsNeighborIndex.hpp)
|
||||
|
||||
target_link_libraries(its_neighbor_index libslic3r admesh)
|
||||
|
||||
if (WIN32)
|
||||
prusaslicer_copy_dlls(its_neighbor_index)
|
||||
endif()
|
||||
613
sandboxes/its_neighbor_index/ItsNeighborIndex.cpp
Normal file
613
sandboxes/its_neighbor_index/ItsNeighborIndex.cpp
Normal file
@@ -0,0 +1,613 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
|
||||
#include "ItsNeighborIndex.hpp"
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
#include "libslic3r/Execution/ExecutionSeq.hpp"
|
||||
|
||||
#include "tbb/parallel_sort.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
FaceNeighborIndex its_create_neighbors_index_1(const indexed_triangle_set &its)
|
||||
{
|
||||
// Just to be clear what type of object are we referencing
|
||||
using FaceID = size_t;
|
||||
using VertexID = uint64_t;
|
||||
using EdgeID = uint64_t;
|
||||
|
||||
constexpr auto UNASSIGNED = std::numeric_limits<FaceID>::max();
|
||||
|
||||
struct Edge // Will contain IDs of the two facets touching this edge
|
||||
{
|
||||
FaceID first, second;
|
||||
Edge() : first{UNASSIGNED}, second{UNASSIGNED} {}
|
||||
void assign(FaceID fid)
|
||||
{
|
||||
first == UNASSIGNED ? first = fid : second = fid;
|
||||
}
|
||||
};
|
||||
|
||||
// All vertex IDs will fit into this number of bits. (Used for hashing)
|
||||
const int max_vertex_id_bits = std::ceil(std::log2(its.vertices.size()));
|
||||
assert(max_vertex_id_bits <= 32);
|
||||
|
||||
std::unordered_map< EdgeID, Edge> edge_index;
|
||||
|
||||
// Edge id is constructed by concatenating two vertex ids, starting with
|
||||
// the lowest in MSB
|
||||
auto hash = [max_vertex_id_bits] (VertexID a, VertexID b) {
|
||||
if (a > b) std::swap(a, b);
|
||||
return (a << max_vertex_id_bits) + b;
|
||||
};
|
||||
|
||||
// Go through all edges of all facets and mark the facets touching each edge
|
||||
for (size_t face_id = 0; face_id < its.indices.size(); ++face_id) {
|
||||
const Vec3i &face = its.indices[face_id];
|
||||
|
||||
EdgeID e1 = hash(face(0), face(1)), e2 = hash(face(1), face(2)),
|
||||
e3 = hash(face(2), face(0));
|
||||
|
||||
edge_index[e1].assign(face_id);
|
||||
edge_index[e2].assign(face_id);
|
||||
edge_index[e3].assign(face_id);
|
||||
}
|
||||
|
||||
FaceNeighborIndex index(its.indices.size());
|
||||
|
||||
// Now collect the neighbors for each facet into the final index
|
||||
for (size_t face_id = 0; face_id < its.indices.size(); ++face_id) {
|
||||
const Vec3i &face = its.indices[face_id];
|
||||
|
||||
EdgeID e1 = hash(face(0), face(1)), e2 = hash(face(1), face(2)),
|
||||
e3 = hash(face(2), face(0));
|
||||
|
||||
const Edge &neighs1 = edge_index[e1];
|
||||
const Edge &neighs2 = edge_index[e2];
|
||||
const Edge &neighs3 = edge_index[e3];
|
||||
|
||||
std::array<size_t, 3> &neighs = index[face_id];
|
||||
neighs[0] = neighs1.first == face_id ? neighs1.second : neighs1.first;
|
||||
neighs[1] = neighs2.first == face_id ? neighs2.second : neighs2.first;
|
||||
neighs[2] = neighs3.first == face_id ? neighs3.second : neighs3.first;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
std::vector<Vec3i> its_create_neighbors_index_2(const indexed_triangle_set &its)
|
||||
{
|
||||
std::vector<Vec3i> out(its.indices.size(), Vec3i(-1, -1, -1));
|
||||
|
||||
// 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); }
|
||||
};
|
||||
std::vector<EdgeToFace> edges_map;
|
||||
edges_map.assign(its.indices.size() * 3, EdgeToFace());
|
||||
for (uint32_t facet_idx = 0; facet_idx < its.indices.size(); ++ facet_idx)
|
||||
for (int i = 0; i < 3; ++ i) {
|
||||
EdgeToFace &e2f = edges_map[facet_idx * 3 + i];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(edges_map.begin(), edges_map.end());
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// Assign an edge index to the 1st face.
|
||||
// out[edge_i.face](std::abs(edge_i.face_edge) - 1) = num_edges;
|
||||
if (found) {
|
||||
EdgeToFace &edge_j = edges_map[j];
|
||||
out[edge_i.face](std::abs(edge_i.face_edge) - 1) = edge_j.face;
|
||||
out[edge_j.face](std::abs(edge_j.face_edge) - 1) = edge_i.face;
|
||||
// Mark the edge as connected.
|
||||
edge_j.face = -1;
|
||||
}
|
||||
++ num_edges;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<Vec3i> its_create_neighbors_index_3(const indexed_triangle_set &its)
|
||||
{
|
||||
std::vector<Vec3i> out(its.indices.size(), Vec3i(-1, -1, -1));
|
||||
|
||||
// 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); }
|
||||
};
|
||||
std::vector<EdgeToFace> edges_map;
|
||||
edges_map.assign(its.indices.size() * 3, EdgeToFace());
|
||||
for (uint32_t facet_idx = 0; facet_idx < its.indices.size(); ++ facet_idx)
|
||||
for (int i = 0; i < 3; ++ i) {
|
||||
EdgeToFace &e2f = edges_map[facet_idx * 3 + i];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
tbb::parallel_sort(edges_map.begin(), edges_map.end());
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// Assign an edge index to the 1st face.
|
||||
// out[edge_i.face](std::abs(edge_i.face_edge) - 1) = num_edges;
|
||||
if (found) {
|
||||
EdgeToFace &edge_j = edges_map[j];
|
||||
out[edge_i.face](std::abs(edge_i.face_edge) - 1) = edge_j.face;
|
||||
out[edge_j.face](std::abs(edge_j.face_edge) - 1) = edge_i.face;
|
||||
// Mark the edge as connected.
|
||||
edge_j.face = -1;
|
||||
}
|
||||
++ num_edges;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
FaceNeighborIndex its_create_neighbors_index_4(const indexed_triangle_set &its)
|
||||
{
|
||||
// Just to be clear what type of object are we referencing
|
||||
using FaceID = size_t;
|
||||
using VertexID = uint64_t;
|
||||
using EdgeID = uint64_t;
|
||||
|
||||
constexpr auto UNASSIGNED = std::numeric_limits<FaceID>::max();
|
||||
|
||||
struct Edge // Will contain IDs of the two facets touching this edge
|
||||
{
|
||||
FaceID first, second;
|
||||
Edge() : first{UNASSIGNED}, second{UNASSIGNED} {}
|
||||
void assign(FaceID fid)
|
||||
{
|
||||
first == UNASSIGNED ? first = fid : second = fid;
|
||||
}
|
||||
};
|
||||
|
||||
Benchmark bm;
|
||||
bm.start();
|
||||
|
||||
// All vertex IDs will fit into this number of bits. (Used for hashing)
|
||||
// const int max_vertex_id_bits = std::ceil(std::log2(its.vertices.size()));
|
||||
// assert(max_vertex_id_bits <= 32);
|
||||
|
||||
const uint64_t Vn = its.vertices.size();
|
||||
// const uint64_t Fn = 3 * its.indices.size();
|
||||
// double MaxQ = double(Vn) * (Vn + 1) / Fn;
|
||||
// const uint64_t Nq = MaxQ < 0 ? 0 : std::ceil(std::log2(MaxQ));
|
||||
// const uint64_t Nr = std::ceil(std::log2(std::min(Vn * (Vn + 1), Fn)));
|
||||
// const uint64_t Nfn = std::ceil(std::log2(Fn));
|
||||
|
||||
//// const uint64_t max_edge_ids = (uint64_t(1) << (Nq + Nr));
|
||||
// const uint64_t max_edge_ids = MaxQ * Fn + (std::min(Vn * (Vn + 1), Fn)); //(uint64_t(1) << Nfn);
|
||||
const uint64_t Fn = 3 * its.indices.size();
|
||||
std::vector< Edge > edge_index(3 * Fn);
|
||||
|
||||
// Edge id is constructed by concatenating two vertex ids, starting with
|
||||
// the lowest in MSB
|
||||
auto hash = [Vn, Fn /*, Nr*/] (VertexID a, VertexID b) {
|
||||
if (a > b) std::swap(a, b);
|
||||
|
||||
uint64_t C = Vn * a + b;
|
||||
uint64_t Q = C / Fn, R = C % Fn;
|
||||
|
||||
return Q * Fn + R;
|
||||
};
|
||||
|
||||
// Go through all edges of all facets and mark the facets touching each edge
|
||||
for (size_t face_id = 0; face_id < its.indices.size(); ++face_id) {
|
||||
const Vec3i &face = its.indices[face_id];
|
||||
|
||||
EdgeID e1 = hash(face(0), face(1)), e2 = hash(face(1), face(2)),
|
||||
e3 = hash(face(2), face(0));
|
||||
|
||||
edge_index[e1].assign(face_id);
|
||||
edge_index[e2].assign(face_id);
|
||||
edge_index[e3].assign(face_id);
|
||||
}
|
||||
|
||||
FaceNeighborIndex index(its.indices.size());
|
||||
|
||||
// Now collect the neighbors for each facet into the final index
|
||||
for (size_t face_id = 0; face_id < its.indices.size(); ++face_id) {
|
||||
const Vec3i &face = its.indices[face_id];
|
||||
|
||||
EdgeID e1 = hash(face(0), face(1)), e2 = hash(face(1), face(2)),
|
||||
e3 = hash(face(2), face(0));
|
||||
|
||||
const Edge &neighs1 = edge_index[e1];
|
||||
const Edge &neighs2 = edge_index[e2];
|
||||
const Edge &neighs3 = edge_index[e3];
|
||||
|
||||
std::array<size_t, 3> &neighs = index[face_id];
|
||||
neighs[0] = neighs1.first == face_id ? neighs1.second : neighs1.first;
|
||||
neighs[1] = neighs2.first == face_id ? neighs2.second : neighs2.first;
|
||||
neighs[2] = neighs3.first == face_id ? neighs3.second : neighs3.first;
|
||||
}
|
||||
|
||||
bm.stop();
|
||||
|
||||
std::cout << "Creating neighbor index took: " << bm.getElapsedSec() << " seconds." << std::endl;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
// Create an index of faces belonging to each vertex. The returned vector can
|
||||
// be indexed with vertex indices and contains a list of face indices for each
|
||||
// vertex.
|
||||
std::vector<std::vector<size_t>> create_vertex_faces_index(const indexed_triangle_set &its)
|
||||
{
|
||||
std::vector<std::vector<size_t>> index;
|
||||
|
||||
if (! its.vertices.empty()) {
|
||||
size_t res = its.indices.size() / its.vertices.size();
|
||||
index.assign(its.vertices.size(), reserve_vector<size_t>(res));
|
||||
for (size_t fi = 0; fi < its.indices.size(); ++fi) {
|
||||
auto &face = its.indices[fi];
|
||||
index[face(0)].emplace_back(fi);
|
||||
index[face(1)].emplace_back(fi);
|
||||
index[face(2)].emplace_back(fi);
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
//static int get_vertex_index(size_t vertex_index, const stl_triangle_vertex_indices &triangle_indices) {
|
||||
// if (vertex_index == triangle_indices[0]) return 0;
|
||||
// if (vertex_index == triangle_indices[1]) return 1;
|
||||
// if (vertex_index == triangle_indices[2]) return 2;
|
||||
// return -1;
|
||||
//}
|
||||
|
||||
//static Vec2crd get_edge_indices(int edge_index, const stl_triangle_vertex_indices &triangle_indices)
|
||||
//{
|
||||
// int next_edge_index = (edge_index == 2) ? 0 : edge_index + 1;
|
||||
// coord_t vi0 = triangle_indices[edge_index];
|
||||
// coord_t vi1 = triangle_indices[next_edge_index];
|
||||
// return Vec2crd(vi0, vi1);
|
||||
//}
|
||||
|
||||
static std::vector<std::vector<size_t>> create_vertex_faces_index(
|
||||
const std::vector<stl_triangle_vertex_indices>& indices, size_t count_vertices)
|
||||
{
|
||||
if (count_vertices == 0) return {};
|
||||
std::vector<std::vector<size_t>> index;
|
||||
size_t res = indices.size() / count_vertices;
|
||||
index.assign(count_vertices, reserve_vector<size_t>(res));
|
||||
for (size_t fi = 0; fi < indices.size(); ++fi) {
|
||||
auto &face = indices[fi];
|
||||
index[face(0)].emplace_back(fi);
|
||||
index[face(1)].emplace_back(fi);
|
||||
index[face(2)].emplace_back(fi);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
std::vector<Vec3crd> its_create_neighbors_index_5(const indexed_triangle_set &its)
|
||||
{
|
||||
const std::vector<stl_triangle_vertex_indices> &indices = its.indices;
|
||||
size_t vertices_size = its.vertices.size();
|
||||
|
||||
if (indices.empty() || vertices_size == 0) return {};
|
||||
std::vector<std::vector<size_t>> vertex_triangles = create_vertex_faces_index(indices, vertices_size);
|
||||
coord_t no_value = -1;
|
||||
std::vector<Vec3crd> neighbors(indices.size(), Vec3crd(no_value, no_value, no_value));
|
||||
for (const stl_triangle_vertex_indices& triangle_indices : indices) {
|
||||
coord_t index = &triangle_indices - &indices.front();
|
||||
Vec3crd& neighbor = neighbors[index];
|
||||
for (int edge_index = 0; edge_index < 3; ++edge_index) {
|
||||
// check if done
|
||||
coord_t& neighbor_edge = neighbor[edge_index];
|
||||
if (neighbor_edge != no_value) continue;
|
||||
Vec2crd edge_indices = get_edge_indices(edge_index, triangle_indices);
|
||||
// IMPROVE: use same vector for 2 sides of triangle
|
||||
const std::vector<size_t> &faces = vertex_triangles[edge_indices[0]];
|
||||
for (const size_t &face : faces) {
|
||||
if (face <= index) continue;
|
||||
const stl_triangle_vertex_indices &face_indices = indices[face];
|
||||
int vertex_index = get_vertex_index(edge_indices[1], face_indices);
|
||||
// NOT Contain second vertex?
|
||||
if (vertex_index < 0) continue;
|
||||
// Has NOT oposit direction?
|
||||
if (edge_indices[0] != face_indices[(vertex_index + 1) % 3]) continue;
|
||||
neighbor_edge = face;
|
||||
neighbors[face][vertex_index] = index;
|
||||
break;
|
||||
}
|
||||
// must be paired
|
||||
assert(neighbor_edge != no_value);
|
||||
}
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
std::vector<std::array<size_t, 3>> its_create_neighbors_index_6(const indexed_triangle_set &its)
|
||||
{
|
||||
constexpr auto UNASSIGNED_EDGE = std::numeric_limits<uint64_t>::max();
|
||||
constexpr auto UNASSIGNED_FACE = std::numeric_limits<size_t>::max();
|
||||
struct Edge
|
||||
{
|
||||
uint64_t id = UNASSIGNED_EDGE;
|
||||
size_t face_id = UNASSIGNED_FACE;
|
||||
bool operator < (const Edge &e) const { return id < e.id; }
|
||||
};
|
||||
|
||||
const size_t facenum = its.indices.size();
|
||||
|
||||
// All vertex IDs will fit into this number of bits. (Used for hashing)
|
||||
const int max_vertex_id_bits = std::ceil(std::log2(its.vertices.size()));
|
||||
assert(max_vertex_id_bits <= 32);
|
||||
|
||||
// Edge id is constructed by concatenating two vertex ids, starting with
|
||||
// the lowest in MSB
|
||||
auto hash = [max_vertex_id_bits] (uint64_t a, uint64_t b) {
|
||||
if (a > b) std::swap(a, b);
|
||||
return (a << max_vertex_id_bits) + b;
|
||||
};
|
||||
|
||||
std::vector<Edge> edge_map(3 * facenum);
|
||||
|
||||
// Go through all edges of all facets and mark the facets touching each edge
|
||||
for (size_t face_id = 0; face_id < facenum; ++face_id) {
|
||||
const Vec3i &face = its.indices[face_id];
|
||||
|
||||
edge_map[face_id * 3] = {hash(face(0), face(1)), face_id};
|
||||
edge_map[face_id * 3 + 1] = {hash(face(1), face(2)), face_id};
|
||||
edge_map[face_id * 3 + 2] = {hash(face(2), face(0)), face_id};
|
||||
}
|
||||
|
||||
std::sort(edge_map.begin(), edge_map.end());
|
||||
|
||||
std::vector<std::array<size_t, 3>> out(facenum, {UNASSIGNED_FACE, UNASSIGNED_FACE, UNASSIGNED_FACE});
|
||||
|
||||
auto add_neighbor = [](std::array<size_t, 3> &slot, size_t face_id) {
|
||||
if (slot[0] == UNASSIGNED_FACE) { slot[0] = face_id; return; }
|
||||
if (slot[1] == UNASSIGNED_FACE) { slot[1] = face_id; return; }
|
||||
if (slot[2] == UNASSIGNED_FACE) { slot[2] = face_id; return; }
|
||||
};
|
||||
|
||||
for (auto it = edge_map.begin(); it != edge_map.end();) {
|
||||
size_t face_id = it->face_id;
|
||||
uint64_t edge_id = it->id;
|
||||
|
||||
while (++it != edge_map.end() && (it->id == edge_id)) {
|
||||
size_t other_face_id = it->face_id;
|
||||
add_neighbor(out[other_face_id], face_id);
|
||||
add_neighbor(out[face_id], other_face_id);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::array<size_t, 3>> its_create_neighbors_index_7(const indexed_triangle_set &its)
|
||||
{
|
||||
constexpr auto UNASSIGNED_EDGE = std::numeric_limits<uint64_t>::max();
|
||||
constexpr auto UNASSIGNED_FACE = std::numeric_limits<size_t>::max();
|
||||
struct Edge
|
||||
{
|
||||
uint64_t id = UNASSIGNED_EDGE;
|
||||
size_t face_id = UNASSIGNED_FACE;
|
||||
bool operator < (const Edge &e) const { return id < e.id; }
|
||||
};
|
||||
|
||||
const size_t facenum = its.indices.size();
|
||||
|
||||
// All vertex IDs will fit into this number of bits. (Used for hashing)
|
||||
const int max_vertex_id_bits = std::ceil(std::log2(its.vertices.size()));
|
||||
assert(max_vertex_id_bits <= 32);
|
||||
|
||||
// Edge id is constructed by concatenating two vertex ids, starting with
|
||||
// the lowest in MSB
|
||||
auto hash = [max_vertex_id_bits] (uint64_t a, uint64_t b) {
|
||||
if (a > b) std::swap(a, b);
|
||||
return (a << max_vertex_id_bits) + b;
|
||||
};
|
||||
|
||||
std::vector<Edge> edge_map(3 * facenum);
|
||||
|
||||
// Go through all edges of all facets and mark the facets touching each edge
|
||||
for (size_t face_id = 0; face_id < facenum; ++face_id) {
|
||||
const Vec3i &face = its.indices[face_id];
|
||||
|
||||
edge_map[face_id * 3] = {hash(face(0), face(1)), face_id};
|
||||
edge_map[face_id * 3 + 1] = {hash(face(1), face(2)), face_id};
|
||||
edge_map[face_id * 3 + 2] = {hash(face(2), face(0)), face_id};
|
||||
}
|
||||
|
||||
tbb::parallel_sort(edge_map.begin(), edge_map.end());
|
||||
|
||||
std::vector<std::array<size_t, 3>> out(facenum, {UNASSIGNED_FACE, UNASSIGNED_FACE, UNASSIGNED_FACE});
|
||||
|
||||
auto add_neighbor = [](std::array<size_t, 3> &slot, size_t face_id) {
|
||||
if (slot[0] == UNASSIGNED_FACE) { slot[0] = face_id; return; }
|
||||
if (slot[1] == UNASSIGNED_FACE) { slot[1] = face_id; return; }
|
||||
if (slot[2] == UNASSIGNED_FACE) { slot[2] = face_id; return; }
|
||||
};
|
||||
|
||||
for (auto it = edge_map.begin(); it != edge_map.end();) {
|
||||
size_t face_id = it->face_id;
|
||||
uint64_t edge_id = it->id;
|
||||
|
||||
while (++it != edge_map.end() && (it->id == edge_id)) {
|
||||
size_t other_face_id = it->face_id;
|
||||
add_neighbor(out[other_face_id], face_id);
|
||||
add_neighbor(out[face_id], other_face_id);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
FaceNeighborIndex its_create_neighbors_index_8(const indexed_triangle_set &its)
|
||||
{
|
||||
// Just to be clear what type of object are we referencing
|
||||
using FaceID = size_t;
|
||||
using VertexID = uint64_t;
|
||||
using EdgeID = uint64_t;
|
||||
|
||||
constexpr auto UNASSIGNED = std::numeric_limits<FaceID>::max();
|
||||
|
||||
struct Edge // Will contain IDs of the two facets touching this edge
|
||||
{
|
||||
FaceID first, second;
|
||||
Edge() : first{UNASSIGNED}, second{UNASSIGNED} {}
|
||||
void assign(FaceID fid)
|
||||
{
|
||||
first == UNASSIGNED ? first = fid : second = fid;
|
||||
}
|
||||
};
|
||||
|
||||
// All vertex IDs will fit into this number of bits. (Used for hashing)
|
||||
const int max_vertex_id_bits = std::ceil(std::log2(its.vertices.size()));
|
||||
assert(max_vertex_id_bits <= 32);
|
||||
|
||||
std::map< EdgeID, Edge > edge_index;
|
||||
|
||||
// Edge id is constructed by concatenating two vertex ids, starting with
|
||||
// the lowest in MSB
|
||||
auto hash = [max_vertex_id_bits] (VertexID a, VertexID b) {
|
||||
if (a > b) std::swap(a, b);
|
||||
return (a << max_vertex_id_bits) + b;
|
||||
};
|
||||
|
||||
// Go through all edges of all facets and mark the facets touching each edge
|
||||
for (size_t face_id = 0; face_id < its.indices.size(); ++face_id) {
|
||||
const Vec3i &face = its.indices[face_id];
|
||||
|
||||
EdgeID e1 = hash(face(0), face(1)), e2 = hash(face(1), face(2)),
|
||||
e3 = hash(face(2), face(0));
|
||||
|
||||
edge_index[e1].assign(face_id);
|
||||
edge_index[e2].assign(face_id);
|
||||
edge_index[e3].assign(face_id);
|
||||
}
|
||||
|
||||
FaceNeighborIndex index(its.indices.size());
|
||||
|
||||
// Now collect the neighbors for each facet into the final index
|
||||
for (size_t face_id = 0; face_id < its.indices.size(); ++face_id) {
|
||||
const Vec3i &face = its.indices[face_id];
|
||||
|
||||
EdgeID e1 = hash(face(0), face(1)), e2 = hash(face(1), face(2)),
|
||||
e3 = hash(face(2), face(0));
|
||||
|
||||
const Edge &neighs1 = edge_index[e1];
|
||||
const Edge &neighs2 = edge_index[e2];
|
||||
const Edge &neighs3 = edge_index[e3];
|
||||
|
||||
std::array<size_t, 3> &neighs = index[face_id];
|
||||
neighs[0] = neighs1.first == face_id ? neighs1.second : neighs1.first;
|
||||
neighs[1] = neighs2.first == face_id ? neighs2.second : neighs2.first;
|
||||
neighs[2] = neighs3.first == face_id ? neighs3.second : neighs3.first;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
std::vector<Vec3crd> its_create_neighbors_index_9(const indexed_triangle_set &its)
|
||||
{
|
||||
return create_face_neighbors_index(ex_seq, its);
|
||||
}
|
||||
|
||||
std::vector<Vec3i> its_create_neighbors_index_10(const indexed_triangle_set &its)
|
||||
{
|
||||
return create_face_neighbors_index(ex_tbb, its);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
19
sandboxes/its_neighbor_index/ItsNeighborIndex.hpp
Normal file
19
sandboxes/its_neighbor_index/ItsNeighborIndex.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include "libslic3r/MeshSplitImpl.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
using FaceNeighborIndex = std::vector<std::array<size_t, 3>>;
|
||||
FaceNeighborIndex its_create_neighbors_index_1(const indexed_triangle_set &its);
|
||||
std::vector<Vec3i> its_create_neighbors_index_2(const indexed_triangle_set &its);
|
||||
std::vector<Vec3i> its_create_neighbors_index_3(const indexed_triangle_set &its);
|
||||
FaceNeighborIndex its_create_neighbors_index_4(const indexed_triangle_set &its);
|
||||
//FaceNeighborIndex its_create_neighbors_index_4(const indexed_triangle_set &its);
|
||||
std::vector<Vec3crd> its_create_neighbors_index_5(const indexed_triangle_set &its);
|
||||
std::vector<std::array<size_t, 3>> its_create_neighbors_index_6(const indexed_triangle_set &its);
|
||||
std::vector<std::array<size_t, 3>> its_create_neighbors_index_7(const indexed_triangle_set &its);
|
||||
FaceNeighborIndex its_create_neighbors_index_8(const indexed_triangle_set &its);
|
||||
std::vector<Vec3crd> its_create_neighbors_index_9(const indexed_triangle_set &its);
|
||||
std::vector<Vec3i> its_create_neighbors_index_10(const indexed_triangle_set &its);
|
||||
|
||||
std::vector<std::vector<size_t>> create_vertex_faces_index(const indexed_triangle_set &its);
|
||||
}
|
||||
273
sandboxes/its_neighbor_index/main.cpp
Normal file
273
sandboxes/its_neighbor_index/main.cpp
Normal file
@@ -0,0 +1,273 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <random>
|
||||
|
||||
#include "ItsNeighborIndex.hpp"
|
||||
|
||||
#include "libnest2d/tools/benchmark.h"
|
||||
#include "libnest2d/utils/metaloop.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum { IndexCreation, Split };
|
||||
struct MeasureResult
|
||||
{
|
||||
static constexpr const char * Names[] = {
|
||||
"Index creation [s]",
|
||||
"Split [s]"
|
||||
};
|
||||
|
||||
double measurements[std::size(Names)] = {0.};
|
||||
};
|
||||
|
||||
template<class IndexCreatorFn>
|
||||
static MeasureResult measure_index(const indexed_triangle_set &its, IndexCreatorFn fn)
|
||||
{
|
||||
Benchmark b;
|
||||
|
||||
MeasureResult r;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
b.start();
|
||||
ItsNeighborsWrapper itsn{its, fn(its)};
|
||||
b.stop();
|
||||
|
||||
r.measurements[IndexCreation] += b.getElapsedSec();
|
||||
|
||||
b.start();
|
||||
auto res = its_split(itsn);
|
||||
b.stop();
|
||||
|
||||
// if (res.size() != 2 || res[0].indices.size() != res[1].indices.size() )
|
||||
// std::cerr << "Something is wrong, split result invalid" << std::endl;
|
||||
|
||||
r.measurements[Split] += b.getElapsedSec();
|
||||
}
|
||||
|
||||
r.measurements[IndexCreation] /= 10;
|
||||
r.measurements[Split] /= 10;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
const auto Seed = 0;// std::random_device{}();
|
||||
|
||||
static indexed_triangle_set make_sphere_rnd(double radius, double detail)
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
auto sphere = its_make_sphere(radius, detail);
|
||||
|
||||
auto vfidx = create_vertex_faces_index(sphere);
|
||||
|
||||
const size_t vertexnum = sphere.vertices.size();
|
||||
const size_t facenum = sphere.indices.size();
|
||||
|
||||
std::mt19937 rng{Seed};
|
||||
std::uniform_int_distribution<size_t> distv(sphere.vertices.size() / 2, sphere.vertices.size() - 1);
|
||||
std::uniform_int_distribution<size_t> distf(sphere.indices.size() / 2, sphere.indices.size() - 1) ;
|
||||
|
||||
std::vector<bool> was(vertexnum / 2, false);
|
||||
|
||||
for (size_t i = 0; i < vertexnum / 2; ++i) {
|
||||
size_t image = distv(rng);
|
||||
if (was[image - vertexnum / 2]) continue;
|
||||
was[image - vertexnum / 2] = true;
|
||||
|
||||
std::swap(sphere.vertices[i], sphere.vertices[image]);
|
||||
for (size_t face_id : vfidx[i]) {
|
||||
for (int &vi : sphere.indices[face_id])
|
||||
if (vi == int(i)) vi = image;
|
||||
}
|
||||
|
||||
for (size_t face_id : vfidx[image]) {
|
||||
for (int &vi : sphere.indices[face_id])
|
||||
if (vi == int(image)) vi = i;
|
||||
}
|
||||
|
||||
std::swap(vfidx[i], vfidx[image]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < facenum / 2; ++i) {
|
||||
size_t image = distf(rng);
|
||||
std::swap(sphere.indices[i], sphere.indices[image]);
|
||||
}
|
||||
|
||||
return sphere;
|
||||
}
|
||||
|
||||
static indexed_triangle_set two_spheres(double detail)
|
||||
{
|
||||
auto sphere1 = make_sphere_rnd(10., 2 * PI / detail), sphere2 = sphere1;
|
||||
|
||||
its_transform(sphere1, identity3f().translate(Vec3f{-5.f, 0.f, 0.f}));
|
||||
its_transform(sphere2, identity3f().translate(Vec3f{5.f, 0.f, 0.f}));
|
||||
|
||||
its_merge(sphere1, sphere2);
|
||||
|
||||
return sphere1;
|
||||
}
|
||||
|
||||
static indexed_triangle_set make_spheres(unsigned N, double detail)
|
||||
{
|
||||
indexed_triangle_set ret, sphere = make_sphere_rnd(10., 2. * PI / detail);
|
||||
|
||||
for (unsigned i = 0u ; i < N; ++i)
|
||||
its_merge(ret, sphere);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr double sq2 = std::sqrt(2.);
|
||||
|
||||
static const std::pair<const std::string, indexed_triangle_set> ToMeasure[] = {
|
||||
// {"two_spheres_1x", two_spheres(60.)},
|
||||
// {"two_spheres_2x", two_spheres(120.)},
|
||||
// {"two_spheres_4x", two_spheres(240.)},
|
||||
// {"two_spheres_8x", two_spheres(480.)},
|
||||
// {"two_spheres_16x", two_spheres(2 * 480.)},
|
||||
// {"two_spheres_32x", two_spheres(2 * 2 * 480.)},
|
||||
|
||||
{"two_spheres_1x", two_spheres(60.)},
|
||||
{"two_spheres_2x", two_spheres(sq2 * 60.)},
|
||||
{"two_spheres_4x", two_spheres(2 * 60.)},
|
||||
{"two_spheres_8x", two_spheres(sq2 * 2. * 60.)},
|
||||
{"two_spheres_16x", two_spheres(4. * 60.)},
|
||||
{"two_spheres_32x", two_spheres(sq2 * 4. * 60.)},
|
||||
{"two_spheres_64x", two_spheres(8. * 60.)},
|
||||
{"two_spheres_128x", two_spheres(sq2 * 8. * 60.)},
|
||||
{"two_spheres_256x", two_spheres(16. * 60.)},
|
||||
{"two_spheres_512x", two_spheres(sq2 * 16. * 60.)},
|
||||
|
||||
{"2_spheres", make_spheres(2, 60.)},
|
||||
{"4_spheres", make_spheres(4, 60.)},
|
||||
{"8_spheres", make_spheres(8, 60.)},
|
||||
{"16_spheres", make_spheres(16, 60.)},
|
||||
{"32_spheres", make_spheres(32, 60.)},
|
||||
{"64_spheres", make_spheres(64, 60.)},
|
||||
{"128_spheres", make_spheres(128, 60.)},
|
||||
{"256_spheres", make_spheres(256, 60.)},
|
||||
{"512_spheres", make_spheres(512, 60.)},
|
||||
{"1024_spheres", make_spheres(1024, 60.)}
|
||||
};
|
||||
|
||||
static const auto IndexFunctions = std::make_tuple(
|
||||
std::make_pair("tamas's unordered_map based", [](const auto &its) { return measure_index(its, its_create_neighbors_index_1); }),
|
||||
std::make_pair("vojta std::sort based", [](const auto &its) { return measure_index(its, its_create_neighbors_index_2); }),
|
||||
std::make_pair("vojta tbb::parallel_sort based", [](const auto &its) { return measure_index(its, its_create_neighbors_index_3); }),
|
||||
std::make_pair("filip's vertex->face based", [](const auto &its) { return measure_index(its, its_create_neighbors_index_5); }),
|
||||
std::make_pair("vojta's vertex->face", [](const auto &its) { return measure_index(its, its_create_neighbors_index_9); }),
|
||||
std::make_pair("vojta's vertex->face parallel", [](const auto &its) { return measure_index(its, its_create_neighbors_index_10); }),
|
||||
std::make_pair("tamas's std::sort based", [](const auto &its) { return measure_index(its, its_create_neighbors_index_6); }),
|
||||
std::make_pair("tamas's tbb::parallel_sort based", [](const auto &its) { return measure_index(its, its_create_neighbors_index_7); }),
|
||||
std::make_pair("tamas's map based", [](const auto &its) { return measure_index(its, its_create_neighbors_index_8); }),
|
||||
std::make_pair("TriangleMesh split", [](const auto &its) {
|
||||
|
||||
MeasureResult r;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
TriangleMesh m{its};
|
||||
Benchmark b;
|
||||
|
||||
b.start();
|
||||
m.repair(); // FIXME: this does more than just create neighborhood map
|
||||
b.stop();
|
||||
r.measurements[IndexCreation] += b.getElapsedSec();
|
||||
|
||||
b.start();
|
||||
auto res = m.split();
|
||||
b.stop();
|
||||
r.measurements[Split] += b.getElapsedSec();
|
||||
|
||||
// if (res.size() != 2 || res[0]->size() != res[1]->size())
|
||||
// std::cerr << "Something is wrong, split result invalid" << std::endl;
|
||||
}
|
||||
r.measurements[IndexCreation] /= 10;
|
||||
r.measurements[Split] /= 10;
|
||||
|
||||
return r;
|
||||
})
|
||||
|
||||
// std::make_pair("Vojta's vertex->face index", [](const auto &its){
|
||||
// Benchmark b;
|
||||
// b.start();
|
||||
// auto index = create_vertex_faces_index(its);
|
||||
// b.stop();
|
||||
|
||||
// if (index.size() != its.vertices.size())
|
||||
// std::cerr << "Something went wrong!";
|
||||
|
||||
// return MeasureResult{b.getElapsedSec(), 0., 0.};
|
||||
// }),
|
||||
// std::make_pair("Tamas's vertex->face index", [](const auto &its){
|
||||
// Benchmark b;
|
||||
// b.start();
|
||||
// VertexFaceIndex index{its};
|
||||
// b.stop();
|
||||
|
||||
// if (index.size() < its.vertices.size())
|
||||
// std::cerr << "Something went wrong!";
|
||||
|
||||
// return MeasureResult{b.getElapsedSec(), 0., 0.};
|
||||
// })
|
||||
);
|
||||
|
||||
static constexpr size_t IndexFuncNum = std::tuple_size_v<decltype (IndexFunctions)>;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
int main(const int argc, const char * argv[])
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
std::array<MeasureResult, IndexFuncNum> results[std::size(ToMeasure)];
|
||||
std::array<std::string, IndexFuncNum> funcnames;
|
||||
|
||||
for (size_t i = 0; i < std::size(ToMeasure); ++i) {
|
||||
auto &m = ToMeasure[i];
|
||||
auto &name = m.first;
|
||||
auto &mesh = m.second;
|
||||
|
||||
// its_write_obj(mesh, (std::string(name) + ".obj").c_str());
|
||||
|
||||
std::cout << "Mesh " << name << " has " << mesh.indices.size() << " faces and " << mesh.vertices.size() << " vertices." << std::endl;
|
||||
libnest2d::opt::metaloop::apply([&mesh, i, &results, &funcnames](int N, auto &e) {
|
||||
MeasureResult r = e.second(mesh);
|
||||
funcnames[N] = e.first;
|
||||
results[i][N] = r;
|
||||
}, IndexFunctions);
|
||||
}
|
||||
|
||||
std::string outfilename = "out.csv";
|
||||
std::fstream outfile;
|
||||
if (argc > 1) {
|
||||
outfilename = argv[1];
|
||||
outfile.open(outfilename, std::fstream::out);
|
||||
std::cout << outfilename << " will be used" << std::endl;
|
||||
}
|
||||
|
||||
std::ostream &out = outfile.is_open() ? outfile : std::cout;
|
||||
|
||||
for (size_t m = 0; m < std::size(MeasureResult::Names); ++m) {
|
||||
out << MeasureResult::Names[m] << "\n";
|
||||
out << std::endl;
|
||||
out << "model;" ;
|
||||
for (const std::string &funcname : funcnames) {
|
||||
out << funcname << ";";
|
||||
}
|
||||
|
||||
out << std::endl;
|
||||
|
||||
for (size_t i = 0; i < std::size(ToMeasure); ++i) {
|
||||
const auto &result_row = results[i];
|
||||
const std::string &name = ToMeasure[i].first;
|
||||
out << name << ";";
|
||||
for (auto &r : result_row)
|
||||
out << r.measurements[m] << ";";
|
||||
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
7
sandboxes/meshboolean/CMakeLists.txt
Normal file
7
sandboxes/meshboolean/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
add_executable(meshboolean MeshBoolean.cpp)
|
||||
|
||||
target_link_libraries(meshboolean libslic3r)
|
||||
|
||||
if (WIN32)
|
||||
prusaslicer_copy_dlls(meshboolean)
|
||||
endif()
|
||||
43
sandboxes/meshboolean/MeshBoolean.cpp
Normal file
43
sandboxes/meshboolean/MeshBoolean.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/SLAPrint.hpp>
|
||||
#include <libslic3r/SLAPrintSteps.hpp>
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
|
||||
#include <libnest2d/tools/benchmark.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
int main(const int argc, const char * argv[])
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
if (argc <= 1) {
|
||||
std::cout << "Usage: meshboolean <input_file.3mf>" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
TriangleMesh input;
|
||||
|
||||
input.ReadSTLFile(argv[1]);
|
||||
|
||||
Benchmark bench;
|
||||
|
||||
bench.start();
|
||||
bool fckd = MeshBoolean::cgal::does_self_intersect(input);
|
||||
bench.stop();
|
||||
|
||||
std::cout << "Self intersect test: " << fckd << " duration: " << bench.getElapsedSec() << std::endl;
|
||||
|
||||
bench.start();
|
||||
MeshBoolean::self_union(input);
|
||||
bench.stop();
|
||||
|
||||
std::cout << "Self union duration: " << bench.getElapsedSec() << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
30
sandboxes/opencsg/CMakeLists.txt
Normal file
30
sandboxes/opencsg/CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
project(OpenCSG-example)
|
||||
|
||||
add_executable(opencsg_example WIN32
|
||||
main.cpp
|
||||
Engine.hpp Engine.cpp
|
||||
ShaderCSGDisplay.hpp ShaderCSGDisplay.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/Jobs/Job.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/ProgressStatusBar.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.cpp)
|
||||
|
||||
find_package(wxWidgets 3.1 REQUIRED COMPONENTS core base gl html)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(GLEW REQUIRED)
|
||||
find_package(OpenCSG REQUIRED)
|
||||
include(${wxWidgets_USE_FILE})
|
||||
|
||||
target_link_libraries(opencsg_example libslic3r)
|
||||
target_include_directories(opencsg_example PRIVATE ${wxWidgets_INCLUDE_DIRS})
|
||||
target_compile_definitions(opencsg_example PRIVATE ${wxWidgets_DEFINITIONS})
|
||||
|
||||
slic3r_remap_configs(OpenCSG::opencsg RelWithDebInfo Release)
|
||||
target_link_libraries(opencsg_example ${wxWidgets_LIBRARIES}
|
||||
OpenCSG::opencsg
|
||||
GLEW::GLEW
|
||||
OpenGL::GL
|
||||
#-lXrandr -lXext -lX11
|
||||
)
|
||||
495
sandboxes/opencsg/Engine.cpp
Normal file
495
sandboxes/opencsg/Engine.cpp
Normal file
@@ -0,0 +1,495 @@
|
||||
#include "Engine.hpp"
|
||||
#include <libslic3r/Utils.hpp>
|
||||
#include <libslic3r/SLAPrint.hpp>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define HAS_GLSAFE
|
||||
#endif
|
||||
|
||||
#ifdef HAS_GLSAFE
|
||||
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
|
||||
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
|
||||
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
||||
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
||||
|
||||
void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name)
|
||||
{
|
||||
GLenum err = glGetError();
|
||||
if (err == GL_NO_ERROR)
|
||||
return;
|
||||
const char *sErr = 0;
|
||||
switch (err) {
|
||||
case GL_INVALID_ENUM: sErr = "Invalid Enum"; break;
|
||||
case GL_INVALID_VALUE: sErr = "Invalid Value"; break;
|
||||
// be aware that GL_INVALID_OPERATION is generated if glGetError is executed between the execution of glBegin and the corresponding execution of glEnd
|
||||
case GL_INVALID_OPERATION: sErr = "Invalid Operation"; break;
|
||||
case GL_STACK_OVERFLOW: sErr = "Stack Overflow"; break;
|
||||
case GL_STACK_UNDERFLOW: sErr = "Stack Underflow"; break;
|
||||
case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break;
|
||||
default: sErr = "Unknown"; break;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
|
||||
assert(false);
|
||||
}
|
||||
|
||||
#else
|
||||
inline void glAssertRecentCall() { }
|
||||
#define glsafe(cmd) cmd
|
||||
#define glcheck()
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace GL {
|
||||
|
||||
Scene::Scene() = default;
|
||||
Scene::~Scene() = default;
|
||||
|
||||
void CSGDisplay::render_scene()
|
||||
{
|
||||
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
|
||||
glsafe(::glColor4fv(color));
|
||||
|
||||
if (m_csgsettings.is_enabled()) {
|
||||
OpenCSG::render(m_scene_cache.primitives_csg);
|
||||
glDepthFunc(GL_EQUAL);
|
||||
}
|
||||
|
||||
for (auto& p : m_scene_cache.primitives_csg) p->render();
|
||||
if (m_csgsettings.is_enabled()) glDepthFunc(GL_LESS);
|
||||
|
||||
for (auto& p : m_scene_cache.primitives_free) p->render();
|
||||
|
||||
glFlush();
|
||||
}
|
||||
|
||||
void Scene::set_print(std::unique_ptr<SLAPrint> &&print)
|
||||
{
|
||||
m_print = std::move(print);
|
||||
|
||||
// Notify displays
|
||||
call(&Listener::on_scene_updated, m_listeners, *this);
|
||||
}
|
||||
|
||||
BoundingBoxf3 Scene::get_bounding_box() const
|
||||
{
|
||||
return m_print->model().bounding_box();
|
||||
}
|
||||
|
||||
void CSGDisplay::SceneCache::clear()
|
||||
{
|
||||
primitives_csg.clear();
|
||||
primitives_free.clear();
|
||||
primitives.clear();
|
||||
}
|
||||
|
||||
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh)
|
||||
{
|
||||
auto p = std::make_shared<Primitive>();
|
||||
p->load_mesh(mesh);
|
||||
primitives.emplace_back(p);
|
||||
primitives_free.emplace_back(p.get());
|
||||
return p;
|
||||
}
|
||||
|
||||
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh,
|
||||
OpenCSG::Operation o,
|
||||
unsigned c)
|
||||
{
|
||||
auto p = std::make_shared<Primitive>(o, c);
|
||||
p->load_mesh(mesh);
|
||||
primitives.emplace_back(p);
|
||||
primitives_csg.emplace_back(p.get());
|
||||
return p;
|
||||
}
|
||||
|
||||
void IndexedVertexArray::push_geometry(float x, float y, float z, float nx, float ny, float nz)
|
||||
{
|
||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||
if (this->vertices_and_normals_interleaved_VBO_id != 0)
|
||||
return;
|
||||
|
||||
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
|
||||
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
|
||||
this->vertices_and_normals_interleaved.emplace_back(nx);
|
||||
this->vertices_and_normals_interleaved.emplace_back(ny);
|
||||
this->vertices_and_normals_interleaved.emplace_back(nz);
|
||||
this->vertices_and_normals_interleaved.emplace_back(x);
|
||||
this->vertices_and_normals_interleaved.emplace_back(y);
|
||||
this->vertices_and_normals_interleaved.emplace_back(z);
|
||||
|
||||
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
|
||||
}
|
||||
|
||||
void IndexedVertexArray::push_triangle(int idx1, int idx2, int idx3) {
|
||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||
if (this->vertices_and_normals_interleaved_VBO_id != 0)
|
||||
return;
|
||||
|
||||
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
|
||||
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
|
||||
this->triangle_indices.emplace_back(idx1);
|
||||
this->triangle_indices.emplace_back(idx2);
|
||||
this->triangle_indices.emplace_back(idx3);
|
||||
this->triangle_indices_size = this->triangle_indices.size();
|
||||
}
|
||||
|
||||
void IndexedVertexArray::load_mesh(const TriangleMesh &mesh)
|
||||
{
|
||||
assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0);
|
||||
assert(quad_indices.empty() && triangle_indices_size == 0);
|
||||
assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size());
|
||||
|
||||
this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
|
||||
|
||||
int vertices_count = 0;
|
||||
for (size_t i = 0; i < mesh.facets_count(); ++i) {
|
||||
const stl_facet &facet = mesh.stl.facet_start[i];
|
||||
for (int j = 0; j < 3; ++j)
|
||||
this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
|
||||
|
||||
this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
|
||||
vertices_count += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void IndexedVertexArray::finalize_geometry()
|
||||
{
|
||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||
assert(this->triangle_indices_VBO_id == 0);
|
||||
assert(this->quad_indices_VBO_id == 0);
|
||||
|
||||
if (!this->vertices_and_normals_interleaved.empty()) {
|
||||
glsafe(
|
||||
::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
|
||||
this->vertices_and_normals_interleaved_VBO_id));
|
||||
glsafe(
|
||||
::glBufferData(GL_ARRAY_BUFFER,
|
||||
GLsizeiptr(
|
||||
this->vertices_and_normals_interleaved.size() *
|
||||
4),
|
||||
this->vertices_and_normals_interleaved.data(),
|
||||
GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->vertices_and_normals_interleaved.clear();
|
||||
}
|
||||
if (!this->triangle_indices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
this->triangle_indices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
GLsizeiptr(this->triangle_indices.size() * 4),
|
||||
this->triangle_indices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
this->triangle_indices.clear();
|
||||
}
|
||||
if (!this->quad_indices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
this->quad_indices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
GLsizeiptr(this->quad_indices.size() * 4),
|
||||
this->quad_indices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
this->quad_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void IndexedVertexArray::release_geometry()
|
||||
{
|
||||
if (this->vertices_and_normals_interleaved_VBO_id) {
|
||||
glsafe(
|
||||
::glDeleteBuffers(1,
|
||||
&this->vertices_and_normals_interleaved_VBO_id));
|
||||
this->vertices_and_normals_interleaved_VBO_id = 0;
|
||||
}
|
||||
if (this->triangle_indices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->triangle_indices_VBO_id));
|
||||
this->triangle_indices_VBO_id = 0;
|
||||
}
|
||||
if (this->quad_indices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->quad_indices_VBO_id));
|
||||
this->quad_indices_VBO_id = 0;
|
||||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void IndexedVertexArray::render() const
|
||||
{
|
||||
assert(this->vertices_and_normals_interleaved_VBO_id != 0);
|
||||
assert(this->triangle_indices_VBO_id != 0 ||
|
||||
this->quad_indices_VBO_id != 0);
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
|
||||
this->vertices_and_normals_interleaved_VBO_id));
|
||||
glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float),
|
||||
reinterpret_cast<const void *>(3 * sizeof(float))));
|
||||
glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr));
|
||||
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
||||
|
||||
// Render using the Vertex Buffer Objects.
|
||||
if (this->triangle_indices_size > 0) {
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
this->triangle_indices_VBO_id));
|
||||
glsafe(::glDrawElements(GL_TRIANGLES,
|
||||
GLsizei(this->triangle_indices_size),
|
||||
GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
if (this->quad_indices_size > 0) {
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
this->quad_indices_VBO_id));
|
||||
glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size),
|
||||
GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
void IndexedVertexArray::clear() {
|
||||
this->vertices_and_normals_interleaved.clear();
|
||||
this->triangle_indices.clear();
|
||||
this->quad_indices.clear();
|
||||
vertices_and_normals_interleaved_size = 0;
|
||||
triangle_indices_size = 0;
|
||||
quad_indices_size = 0;
|
||||
}
|
||||
|
||||
void IndexedVertexArray::shrink_to_fit() {
|
||||
this->vertices_and_normals_interleaved.shrink_to_fit();
|
||||
this->triangle_indices.shrink_to_fit();
|
||||
this->quad_indices.shrink_to_fit();
|
||||
}
|
||||
|
||||
void Volume::render()
|
||||
{
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glMultMatrixd(m_trafo.get_matrix().data()));
|
||||
m_geom.render();
|
||||
glsafe(::glPopMatrix());
|
||||
}
|
||||
|
||||
void Display::clear_screen()
|
||||
{
|
||||
glViewport(0, 0, GLsizei(m_size.x()), GLsizei(m_size.y()));
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
}
|
||||
|
||||
Display::~Display()
|
||||
{
|
||||
OpenCSG::freeResources();
|
||||
}
|
||||
|
||||
void Display::set_active(long width, long height)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
glewInit();
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
// gray background
|
||||
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
|
||||
|
||||
// Enable two OpenGL lights
|
||||
GLfloat light_diffuse[] = { 1.0f, 1.0f, 0.0f, 1.0f}; // White diffuse light
|
||||
GLfloat light_position0[] = {-1.0f, -1.0f, -1.0f, 0.0f}; // Infinite light location
|
||||
GLfloat light_position1[] = { 1.0f, 1.0f, 1.0f, 0.0f}; // Infinite light location
|
||||
|
||||
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
|
||||
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
|
||||
glEnable(GL_LIGHT0);
|
||||
glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
|
||||
glLightfv(GL_LIGHT1, GL_POSITION, light_position1);
|
||||
glEnable(GL_LIGHT1);
|
||||
glEnable(GL_LIGHTING);
|
||||
glEnable(GL_NORMALIZE);
|
||||
|
||||
// Use depth buffering for hidden surface elimination
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
|
||||
set_screen_size(width, height);
|
||||
}
|
||||
|
||||
void Display::set_screen_size(long width, long height)
|
||||
{
|
||||
if (m_size.x() != width || m_size.y() != height)
|
||||
m_camera->set_screen(width, height);
|
||||
|
||||
m_size = {width, height};
|
||||
}
|
||||
|
||||
void Display::repaint()
|
||||
{
|
||||
clear_screen();
|
||||
|
||||
m_camera->view();
|
||||
render_scene();
|
||||
|
||||
m_fps_counter.update();
|
||||
|
||||
swap_buffers();
|
||||
}
|
||||
|
||||
void Controller::on_scene_updated(const Scene &scene)
|
||||
{
|
||||
const SLAPrint *print = scene.get_print();
|
||||
if (!print) return;
|
||||
|
||||
auto bb = scene.get_bounding_box();
|
||||
double d = std::max(std::max(bb.size().x(), bb.size().y()), bb.size().z());
|
||||
m_wheel_pos = long(2 * d);
|
||||
|
||||
call_cameras(&Camera::set_zoom, m_wheel_pos);
|
||||
call(&Display::on_scene_updated, m_displays, scene);
|
||||
}
|
||||
|
||||
void Controller::on_scroll(long v, long d, MouseInput::WheelAxis /*wa*/)
|
||||
{
|
||||
m_wheel_pos += v / d;
|
||||
|
||||
call_cameras(&Camera::set_zoom, m_wheel_pos);
|
||||
call(&Display::repaint, m_displays);
|
||||
}
|
||||
|
||||
void Controller::on_moved_to(long x, long y)
|
||||
{
|
||||
if (m_left_btn) {
|
||||
call_cameras(&Camera::rotate, (Vec2i{x, y} - m_mouse_pos).cast<float>());
|
||||
call(&Display::repaint, m_displays);
|
||||
}
|
||||
|
||||
m_mouse_pos = {x, y};
|
||||
}
|
||||
|
||||
void CSGDisplay::apply_csgsettings(const CSGSettings &settings)
|
||||
{
|
||||
using namespace OpenCSG;
|
||||
|
||||
bool needupdate = m_csgsettings.get_convexity() != settings.get_convexity();
|
||||
|
||||
m_csgsettings = settings;
|
||||
setOption(AlgorithmSetting, m_csgsettings.get_algo());
|
||||
setOption(DepthComplexitySetting, m_csgsettings.get_depth_algo());
|
||||
setOption(DepthBoundsOptimization, m_csgsettings.get_optimization());
|
||||
|
||||
if (needupdate) {
|
||||
for (OpenCSG::Primitive * p : m_scene_cache.primitives_csg)
|
||||
if (p->getConvexity() > 1)
|
||||
p->setConvexity(m_csgsettings.get_convexity());
|
||||
}
|
||||
}
|
||||
|
||||
void CSGDisplay::on_scene_updated(const Scene &scene)
|
||||
{
|
||||
const SLAPrint *print = scene.get_print();
|
||||
if (!print) return;
|
||||
|
||||
m_scene_cache.clear();
|
||||
|
||||
for (const SLAPrintObject *po : print->objects()) {
|
||||
const ModelObject *mo = po->model_object();
|
||||
TriangleMesh msh = mo->raw_mesh();
|
||||
|
||||
sla::DrainHoles holedata = mo->sla_drain_holes;
|
||||
|
||||
for (const ModelInstance *mi : mo->instances) {
|
||||
|
||||
TriangleMesh mshinst = msh;
|
||||
auto interior = po->hollowed_interior_mesh();
|
||||
interior.transform(po->trafo().inverse());
|
||||
|
||||
mshinst.merge(interior);
|
||||
|
||||
mi->transform_mesh(&mshinst);
|
||||
|
||||
auto bb = mshinst.bounding_box();
|
||||
auto center = bb.center().cast<float>();
|
||||
mshinst.translate(-center);
|
||||
|
||||
m_scene_cache.add_mesh(mshinst, OpenCSG::Intersection,
|
||||
m_csgsettings.get_convexity());
|
||||
}
|
||||
|
||||
for (const sla::DrainHole &holept : holedata) {
|
||||
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
|
||||
m_scene_cache.add_mesh(holemesh, OpenCSG::Subtraction, 1);
|
||||
}
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Camera::view()
|
||||
{
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
gluLookAt(0.0, m_zoom, 0.0, /* eye is at (0,zoom,0) */
|
||||
m_referene.x(), m_referene.y(), m_referene.z(),
|
||||
0.0, 0.0, 1.0); /* up is in positive Y direction */
|
||||
|
||||
// TODO Could have been set in prevoius gluLookAt in first argument
|
||||
glRotatef(m_rot.y(), 1.0, 0.0, 0.0);
|
||||
glRotatef(m_rot.x(), 0.0, 0.0, 1.0);
|
||||
|
||||
if (m_clip_z > 0.) {
|
||||
GLdouble plane[] = {0., 0., 1., m_clip_z};
|
||||
glClipPlane(GL_CLIP_PLANE0, plane);
|
||||
glEnable(GL_CLIP_PLANE0);
|
||||
} else {
|
||||
glDisable(GL_CLIP_PLANE0);
|
||||
}
|
||||
}
|
||||
|
||||
void PerspectiveCamera::set_screen(long width, long height)
|
||||
{
|
||||
// Setup the view of the CSG shape
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluPerspective(45.0, width / double(height), .1, 200.0);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
}
|
||||
|
||||
bool enable_multisampling(bool e)
|
||||
{
|
||||
if (!e) { glDisable(GL_MULTISAMPLE); return false; }
|
||||
|
||||
GLint is_ms_context;
|
||||
glGetIntegerv(GL_SAMPLE_BUFFERS, &is_ms_context);
|
||||
|
||||
if (is_ms_context) { glEnable(GL_MULTISAMPLE); return true; }
|
||||
else return false;
|
||||
}
|
||||
|
||||
MouseInput::Listener::~Listener() = default;
|
||||
|
||||
void FpsCounter::update()
|
||||
{
|
||||
++m_frames;
|
||||
|
||||
TimePoint msec = Clock::now();
|
||||
|
||||
double seconds_window = to_sec(msec - m_window);
|
||||
m_fps = 0.5 * m_fps + 0.5 * (m_frames / seconds_window);
|
||||
|
||||
if (to_sec(msec - m_last) >= m_resolution) {
|
||||
m_last = msec;
|
||||
for (auto &l : m_listeners) l(m_fps);
|
||||
}
|
||||
|
||||
if (seconds_window >= m_window_size) {
|
||||
m_frames = 0;
|
||||
m_window = msec;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GL
|
||||
488
sandboxes/opencsg/Engine.hpp
Normal file
488
sandboxes/opencsg/Engine.hpp
Normal file
@@ -0,0 +1,488 @@
|
||||
#ifndef SLIC3R_OCSG_EXMP_ENGINE_HPP
|
||||
#define SLIC3R_OCSG_EXMP_ENGINE_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <opencsg/opencsg.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SLAPrint;
|
||||
|
||||
namespace GL {
|
||||
|
||||
template<class T, class A = std::allocator<T>> using vector = std::vector<T, A>;
|
||||
|
||||
// remove empty weak pointers from a vector
|
||||
template<class L> inline void cleanup(vector<std::weak_ptr<L>> &listeners) {
|
||||
auto it = std::remove_if(listeners.begin(), listeners.end(),
|
||||
[](auto &l) { return !l.lock(); });
|
||||
listeners.erase(it, listeners.end());
|
||||
}
|
||||
|
||||
// Call a class method on each element of a vector of objects (weak pointers)
|
||||
// of the same type.
|
||||
template<class F, class L, class...Args>
|
||||
inline void call(F &&f, vector<std::weak_ptr<L>> &listeners, Args&&... args) {
|
||||
for (auto &l : listeners)
|
||||
if (auto p = l.lock()) ((p.get())->*f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// A representation of a mouse input for the engine.
|
||||
class MouseInput
|
||||
{
|
||||
public:
|
||||
enum WheelAxis { waVertical, waHorizontal };
|
||||
|
||||
// Interface to implement if an object wants to receive notifications
|
||||
// about mouse events.
|
||||
class Listener {
|
||||
public:
|
||||
virtual ~Listener();
|
||||
|
||||
virtual void on_left_click_down() {}
|
||||
virtual void on_left_click_up() {}
|
||||
virtual void on_right_click_down() {}
|
||||
virtual void on_right_click_up() {}
|
||||
virtual void on_double_click() {}
|
||||
virtual void on_scroll(long /*v*/, long /*delta*/, WheelAxis ) {}
|
||||
virtual void on_moved_to(long /*x*/, long /*y*/) {}
|
||||
};
|
||||
|
||||
private:
|
||||
vector<std::weak_ptr<Listener>> m_listeners;
|
||||
|
||||
public:
|
||||
virtual ~MouseInput() = default;
|
||||
|
||||
virtual void left_click_down()
|
||||
{
|
||||
call(&Listener::on_left_click_down, m_listeners);
|
||||
}
|
||||
virtual void left_click_up()
|
||||
{
|
||||
call(&Listener::on_left_click_up, m_listeners);
|
||||
}
|
||||
virtual void right_click_down()
|
||||
{
|
||||
call(&Listener::on_right_click_down, m_listeners);
|
||||
}
|
||||
virtual void right_click_up()
|
||||
{
|
||||
call(&Listener::on_right_click_up, m_listeners);
|
||||
}
|
||||
virtual void double_click()
|
||||
{
|
||||
call(&Listener::on_double_click, m_listeners);
|
||||
}
|
||||
virtual void scroll(long v, long d, WheelAxis wa)
|
||||
{
|
||||
call(&Listener::on_scroll, m_listeners, v, d, wa);
|
||||
}
|
||||
virtual void move_to(long x, long y)
|
||||
{
|
||||
call(&Listener::on_moved_to, m_listeners, x, y);
|
||||
}
|
||||
|
||||
void add_listener(std::shared_ptr<Listener> listener)
|
||||
{
|
||||
m_listeners.emplace_back(listener);
|
||||
cleanup(m_listeners);
|
||||
}
|
||||
};
|
||||
|
||||
// This is a stripped down version of Slic3r::IndexedVertexArray
|
||||
class IndexedVertexArray {
|
||||
public:
|
||||
~IndexedVertexArray() { release_geometry(); }
|
||||
|
||||
// Vertices and their normals, interleaved to be used by void
|
||||
// glInterleavedArrays(GL_N3F_V3F, 0, x)
|
||||
vector<float> vertices_and_normals_interleaved;
|
||||
vector<int> triangle_indices;
|
||||
vector<int> quad_indices;
|
||||
|
||||
// When the geometry data is loaded into the graphics card as Vertex
|
||||
// Buffer Objects, the above mentioned std::vectors are cleared and the
|
||||
// following variables keep their original length.
|
||||
size_t vertices_and_normals_interleaved_size{ 0 };
|
||||
size_t triangle_indices_size{ 0 };
|
||||
size_t quad_indices_size{ 0 };
|
||||
|
||||
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
||||
// Zero if the VBOs are not sent to GPU yet.
|
||||
unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
|
||||
unsigned int triangle_indices_VBO_id{ 0 };
|
||||
unsigned int quad_indices_VBO_id{ 0 };
|
||||
|
||||
|
||||
void push_geometry(float x, float y, float z, float nx, float ny, float nz);
|
||||
|
||||
inline void push_geometry(
|
||||
double x, double y, double z, double nx, double ny, double nz)
|
||||
{
|
||||
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
|
||||
}
|
||||
|
||||
inline void push_geometry(const Vec3d &p, const Vec3d &n)
|
||||
{
|
||||
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
|
||||
}
|
||||
|
||||
void push_triangle(int idx1, int idx2, int idx3);
|
||||
|
||||
void load_mesh(const TriangleMesh &mesh);
|
||||
|
||||
inline bool has_VBOs() const
|
||||
{
|
||||
return vertices_and_normals_interleaved_VBO_id != 0;
|
||||
}
|
||||
|
||||
// Finalize the initialization of the geometry & indices,
|
||||
// upload the geometry and indices to OpenGL VBO objects
|
||||
// and shrink the allocated data, possibly relasing it if it has been
|
||||
// loaded into the VBOs.
|
||||
void finalize_geometry();
|
||||
// Release the geometry data, release OpenGL VBOs.
|
||||
void release_geometry();
|
||||
|
||||
void render() const;
|
||||
|
||||
// Is there any geometry data stored?
|
||||
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
|
||||
|
||||
void clear();
|
||||
|
||||
// Shrink the internal storage to tighly fit the data stored.
|
||||
void shrink_to_fit();
|
||||
};
|
||||
|
||||
// Try to enable or disable multisampling.
|
||||
bool enable_multisampling(bool e = true);
|
||||
|
||||
class Volume {
|
||||
IndexedVertexArray m_geom;
|
||||
Geometry::Transformation m_trafo;
|
||||
|
||||
public:
|
||||
|
||||
void render();
|
||||
|
||||
void translation(const Vec3d &offset) { m_trafo.set_offset(offset); }
|
||||
void rotation(const Vec3d &rot) { m_trafo.set_rotation(rot); }
|
||||
void scale(const Vec3d &scaleing) { m_trafo.set_scaling_factor(scaleing); }
|
||||
void scale(double s) { scale({s, s, s}); }
|
||||
|
||||
inline void load_mesh(const TriangleMesh &mesh)
|
||||
{
|
||||
m_geom.load_mesh(mesh);
|
||||
m_geom.finalize_geometry();
|
||||
}
|
||||
};
|
||||
|
||||
// A primitive that can be used with OpenCSG rendering algorithms.
|
||||
// Does a similar job to GLVolume.
|
||||
class Primitive : public Volume, public OpenCSG::Primitive
|
||||
{
|
||||
public:
|
||||
using OpenCSG::Primitive::Primitive;
|
||||
|
||||
Primitive() : OpenCSG::Primitive(OpenCSG::Intersection, 1) {}
|
||||
|
||||
void render() override { Volume::render(); }
|
||||
};
|
||||
|
||||
// A simple representation of a camera in a 3D scene
|
||||
class Camera {
|
||||
protected:
|
||||
Vec2f m_rot = {0., 0.};
|
||||
Vec3d m_referene = {0., 0., 0.};
|
||||
double m_zoom = 0.;
|
||||
double m_clip_z = 0.;
|
||||
public:
|
||||
|
||||
virtual ~Camera() = default;
|
||||
|
||||
virtual void view();
|
||||
virtual void set_screen(long width, long height) = 0;
|
||||
|
||||
void set_rotation(const Vec2f &rotation) { m_rot = rotation; }
|
||||
void rotate(const Vec2f &rotation) { m_rot += rotation; }
|
||||
void set_zoom(double z) { m_zoom = z; }
|
||||
void set_reference_point(const Vec3d &p) { m_referene = p; }
|
||||
void set_clip_z(double z) { m_clip_z = z; }
|
||||
};
|
||||
|
||||
// Reset a camera object
|
||||
inline void reset(Camera &cam)
|
||||
{
|
||||
cam.set_rotation({0., 0.});
|
||||
cam.set_zoom(0.);
|
||||
cam.set_reference_point({0., 0., 0.});
|
||||
cam.set_clip_z(0.);
|
||||
}
|
||||
|
||||
// Specialization of a camera which shows in perspective projection
|
||||
class PerspectiveCamera: public Camera {
|
||||
public:
|
||||
|
||||
void set_screen(long width, long height) override;
|
||||
};
|
||||
|
||||
// A simple counter of FPS. Subscribed objects will receive updates of the
|
||||
// current fps.
|
||||
class FpsCounter {
|
||||
vector<std::function<void(double)>> m_listeners;
|
||||
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
using Duration = Clock::duration;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
int m_frames = 0;
|
||||
TimePoint m_last = Clock::now(), m_window = m_last;
|
||||
|
||||
double m_resolution = 0.1, m_window_size = 1.0;
|
||||
double m_fps = 0.;
|
||||
|
||||
static double to_sec(Duration d)
|
||||
{
|
||||
return d.count() * double(Duration::period::num) / Duration::period::den;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void update();
|
||||
|
||||
void add_listener(std::function<void(double)> lst)
|
||||
{
|
||||
m_listeners.emplace_back(lst);
|
||||
}
|
||||
|
||||
void clear_listeners() { m_listeners = {}; }
|
||||
|
||||
void set_notification_interval(double seconds);
|
||||
void set_measure_window_size(double seconds);
|
||||
|
||||
double get_notification_interval() const { return m_resolution; }
|
||||
double get_mesure_window_size() const { return m_window_size; }
|
||||
};
|
||||
|
||||
// Collection of the used OpenCSG library settings.
|
||||
class CSGSettings {
|
||||
public:
|
||||
static const constexpr unsigned DEFAULT_CONVEXITY = 10;
|
||||
|
||||
private:
|
||||
OpenCSG::Algorithm m_csgalg = OpenCSG::Algorithm::Automatic;
|
||||
OpenCSG::DepthComplexityAlgorithm m_depth_algo = OpenCSG::NoDepthComplexitySampling;
|
||||
OpenCSG::Optimization m_optim = OpenCSG::OptimizationDefault;
|
||||
bool m_enable = true;
|
||||
unsigned int m_convexity = DEFAULT_CONVEXITY;
|
||||
|
||||
public:
|
||||
int get_algo() const { return int(m_csgalg); }
|
||||
void set_algo(int alg)
|
||||
{
|
||||
if (alg < OpenCSG::Algorithm::AlgorithmUnused)
|
||||
m_csgalg = OpenCSG::Algorithm(alg);
|
||||
}
|
||||
|
||||
int get_depth_algo() const { return int(m_depth_algo); }
|
||||
void set_depth_algo(int alg)
|
||||
{
|
||||
if (alg < OpenCSG::DepthComplexityAlgorithmUnused)
|
||||
m_depth_algo = OpenCSG::DepthComplexityAlgorithm(alg);
|
||||
}
|
||||
|
||||
int get_optimization() const { return int(m_optim); }
|
||||
void set_optimization(int o)
|
||||
{
|
||||
if (o < OpenCSG::Optimization::OptimizationUnused)
|
||||
m_optim = OpenCSG::Optimization(o);
|
||||
}
|
||||
|
||||
void enable_csg(bool en = true) { m_enable = en; }
|
||||
bool is_enabled() const { return m_enable; }
|
||||
|
||||
unsigned get_convexity() const { return m_convexity; }
|
||||
void set_convexity(unsigned c) { m_convexity = c; }
|
||||
};
|
||||
|
||||
// The scene is a wrapper around SLAPrint which holds the data to be visualized.
|
||||
class Scene
|
||||
{
|
||||
std::unique_ptr<SLAPrint> m_print;
|
||||
public:
|
||||
|
||||
// Subscribers will be notified if the model is changed. This might be a
|
||||
// display which will have to load the meshes and repaint itself when
|
||||
// the scene data changes.
|
||||
// eg. We load a new 3mf through the UI, this will notify the controller
|
||||
// associated with the scene and all the displays that the controller is
|
||||
// connected with.
|
||||
class Listener {
|
||||
public:
|
||||
virtual ~Listener() = default;
|
||||
virtual void on_scene_updated(const Scene &scene) = 0;
|
||||
};
|
||||
|
||||
Scene();
|
||||
~Scene();
|
||||
|
||||
void set_print(std::unique_ptr<SLAPrint> &&print);
|
||||
const SLAPrint * get_print() const { return m_print.get(); }
|
||||
|
||||
BoundingBoxf3 get_bounding_box() const;
|
||||
|
||||
void add_listener(std::shared_ptr<Listener> listener)
|
||||
{
|
||||
m_listeners.emplace_back(listener);
|
||||
cleanup(m_listeners);
|
||||
}
|
||||
|
||||
private:
|
||||
vector<std::weak_ptr<Listener>> m_listeners;
|
||||
};
|
||||
|
||||
// The basic Display. This is almost just an interface but will do all the
|
||||
// initialization and show the fps values. Overriding the render_scene is
|
||||
// needed to show the scene content. The specific method of displaying the
|
||||
// scene is up the the particular implementation (OpenCSG or other screen space
|
||||
// boolean algorithms)
|
||||
class Display : public Scene::Listener
|
||||
{
|
||||
protected:
|
||||
Vec2i m_size;
|
||||
bool m_initialized = false;
|
||||
|
||||
std::shared_ptr<Camera> m_camera;
|
||||
FpsCounter m_fps_counter;
|
||||
|
||||
public:
|
||||
|
||||
explicit Display(std::shared_ptr<Camera> camera = nullptr)
|
||||
: m_camera(camera ? camera : std::make_shared<PerspectiveCamera>())
|
||||
{}
|
||||
|
||||
~Display() override;
|
||||
|
||||
std::shared_ptr<const Camera> get_camera() const { return m_camera; }
|
||||
std::shared_ptr<Camera> get_camera() { return m_camera; }
|
||||
void set_camera(std::shared_ptr<Camera> cam) { m_camera = cam; }
|
||||
|
||||
virtual void swap_buffers() = 0;
|
||||
virtual void set_active(long width, long height);
|
||||
virtual void set_screen_size(long width, long height);
|
||||
Vec2i get_screen_size() const { return m_size; }
|
||||
|
||||
virtual void repaint();
|
||||
|
||||
bool is_initialized() const { return m_initialized; }
|
||||
|
||||
virtual void clear_screen();
|
||||
virtual void render_scene() {}
|
||||
|
||||
template<class _FpsCounter> void set_fps_counter(_FpsCounter &&fpsc)
|
||||
{
|
||||
m_fps_counter = std::forward<_FpsCounter>(fpsc);
|
||||
}
|
||||
|
||||
const FpsCounter &get_fps_counter() const { return m_fps_counter; }
|
||||
FpsCounter &get_fps_counter() { return m_fps_counter; }
|
||||
};
|
||||
|
||||
// Special dispaly using OpenCSG for rendering the scene.
|
||||
class CSGDisplay : public Display {
|
||||
protected:
|
||||
CSGSettings m_csgsettings;
|
||||
|
||||
// Cache the renderable primitives. These will be fetched when the scene
|
||||
// is modified.
|
||||
struct SceneCache {
|
||||
vector<std::shared_ptr<Primitive>> primitives;
|
||||
vector<Primitive *> primitives_free;
|
||||
vector<OpenCSG::Primitive *> primitives_csg;
|
||||
|
||||
void clear();
|
||||
|
||||
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh);
|
||||
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh,
|
||||
OpenCSG::Operation op,
|
||||
unsigned covexity);
|
||||
} m_scene_cache;
|
||||
|
||||
public:
|
||||
|
||||
// Receive or apply the new settings.
|
||||
const CSGSettings & get_csgsettings() const { return m_csgsettings; }
|
||||
void apply_csgsettings(const CSGSettings &settings);
|
||||
|
||||
void render_scene() override;
|
||||
|
||||
void on_scene_updated(const Scene &scene) override;
|
||||
};
|
||||
|
||||
|
||||
// The controller is a hub which dispatches mouse events to the connected
|
||||
// displays. It keeps track of the mouse wheel position, the states whether
|
||||
// the mouse is being held, dragged, etc... All the connected displays will
|
||||
// mirror the camera movement (if there is more than one display).
|
||||
class Controller : public std::enable_shared_from_this<Controller>,
|
||||
public MouseInput::Listener,
|
||||
public Scene::Listener
|
||||
{
|
||||
long m_wheel_pos = 0;
|
||||
Vec2i m_mouse_pos, m_mouse_pos_rprev, m_mouse_pos_lprev;
|
||||
bool m_left_btn = false, m_right_btn = false;
|
||||
|
||||
std::shared_ptr<Scene> m_scene;
|
||||
vector<std::weak_ptr<Display>> m_displays;
|
||||
|
||||
// Call a method of Camera on all the cameras of the attached displays
|
||||
template<class F, class...Args>
|
||||
void call_cameras(F &&f, Args&&... args) {
|
||||
for (std::weak_ptr<Display> &l : m_displays)
|
||||
if (auto disp = l.lock()) if (auto cam = disp->get_camera())
|
||||
(cam.get()->*f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// Set the scene that will be controlled.
|
||||
void set_scene(std::shared_ptr<Scene> scene)
|
||||
{
|
||||
m_scene = scene;
|
||||
m_scene->add_listener(shared_from_this());
|
||||
}
|
||||
|
||||
const Scene * get_scene() const { return m_scene.get(); }
|
||||
|
||||
void add_display(std::shared_ptr<Display> disp)
|
||||
{
|
||||
m_displays.emplace_back(disp);
|
||||
cleanup(m_displays);
|
||||
}
|
||||
|
||||
void remove_displays() { m_displays = {}; }
|
||||
|
||||
void on_scene_updated(const Scene &scene) override;
|
||||
|
||||
void on_left_click_down() override { m_left_btn = true; }
|
||||
void on_left_click_up() override { m_left_btn = false; }
|
||||
void on_right_click_down() override { m_right_btn = true; }
|
||||
void on_right_click_up() override { m_right_btn = false; }
|
||||
|
||||
void on_scroll(long v, long d, MouseInput::WheelAxis wa) override;
|
||||
void on_moved_to(long x, long y) override;
|
||||
|
||||
void move_clip_plane(double z) { call_cameras(&Camera::set_clip_z, z); }
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GL
|
||||
#endif // SLIC3R_OCSG_EXMP_ENGINE_HPP
|
||||
63
sandboxes/opencsg/ShaderCSGDisplay.cpp
Normal file
63
sandboxes/opencsg/ShaderCSGDisplay.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "ShaderCSGDisplay.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r { namespace GL {
|
||||
|
||||
void ShaderCSGDisplay::add_mesh(const TriangleMesh &mesh)
|
||||
{
|
||||
auto v = std::make_shared<CSGVolume>();
|
||||
v->load_mesh(mesh);
|
||||
m_volumes.emplace_back(v);
|
||||
}
|
||||
|
||||
void ShaderCSGDisplay::render_scene()
|
||||
{
|
||||
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
|
||||
glColor4fv(color);
|
||||
glDepthFunc(GL_LESS);
|
||||
for (auto &v : m_volumes) v->render();
|
||||
glFlush();
|
||||
}
|
||||
|
||||
void ShaderCSGDisplay::on_scene_updated(const Scene &scene)
|
||||
{
|
||||
// TriangleMesh mesh = print->objects().front()->hollowed_interior_mesh();
|
||||
// Look at CSGDisplay::on_scene_updated to see how its done there.
|
||||
|
||||
const SLAPrint *print = scene.get_print();
|
||||
if (!print) return;
|
||||
|
||||
m_volumes.clear();
|
||||
|
||||
for (const SLAPrintObject *po : print->objects()) {
|
||||
const ModelObject *mo = po->model_object();
|
||||
TriangleMesh msh = mo->raw_mesh();
|
||||
|
||||
sla::DrainHoles holedata = mo->sla_drain_holes;
|
||||
|
||||
for (const ModelInstance *mi : mo->instances) {
|
||||
|
||||
TriangleMesh mshinst = msh;
|
||||
auto interior = po->hollowed_interior_mesh();
|
||||
interior.transform(po->trafo().inverse());
|
||||
|
||||
mshinst.merge(interior);
|
||||
|
||||
mi->transform_mesh(&mshinst);
|
||||
|
||||
auto bb = mshinst.bounding_box();
|
||||
auto center = bb.center().cast<float>();
|
||||
mshinst.translate(-center);
|
||||
|
||||
add_mesh(mshinst);
|
||||
}
|
||||
|
||||
for (const sla::DrainHole &holept : holedata)
|
||||
add_mesh(sla::to_triangle_mesh(holept.to_mesh()));
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GL
|
||||
27
sandboxes/opencsg/ShaderCSGDisplay.hpp
Normal file
27
sandboxes/opencsg/ShaderCSGDisplay.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef SHADERCSGDISPLAY_HPP
|
||||
#define SHADERCSGDISPLAY_HPP
|
||||
|
||||
#include "Engine.hpp"
|
||||
|
||||
namespace Slic3r { namespace GL {
|
||||
|
||||
class CSGVolume: public Volume
|
||||
{
|
||||
// Extend...
|
||||
};
|
||||
|
||||
class ShaderCSGDisplay: public Display {
|
||||
protected:
|
||||
vector<std::shared_ptr<CSGVolume>> m_volumes;
|
||||
|
||||
void add_mesh(const TriangleMesh &mesh);
|
||||
public:
|
||||
|
||||
void render_scene() override;
|
||||
|
||||
void on_scene_updated(const Scene &scene) override;
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif // SHADERCSGDISPLAY_HPP
|
||||
734
sandboxes/opencsg/main.cpp
Normal file
734
sandboxes/opencsg/main.cpp
Normal file
@@ -0,0 +1,734 @@
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "ShaderCSGDisplay.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <opencsg/opencsg.h>
|
||||
// For compilers that support precompilation, includes "wx/wx.h".
|
||||
#include <wx/wxprec.h>
|
||||
#ifndef WX_PRECOMP
|
||||
#include <wx/wx.h>
|
||||
#endif
|
||||
|
||||
#include <wx/slider.h>
|
||||
#include <wx/tglbtn.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#include <wx/cmdline.h>
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
|
||||
#include "slic3r/GUI/Jobs/Job.hpp"
|
||||
#include "slic3r/GUI/ProgressStatusBar.hpp"
|
||||
|
||||
using namespace Slic3r::GL;
|
||||
|
||||
class Renderer {
|
||||
protected:
|
||||
wxGLCanvas *m_canvas;
|
||||
std::shared_ptr<wxGLContext> m_context;
|
||||
public:
|
||||
|
||||
Renderer(wxGLCanvas *c): m_canvas{c} {
|
||||
auto ctx = new wxGLContext(m_canvas);
|
||||
if (!ctx || !ctx->IsOK()) {
|
||||
wxMessageBox("Could not create OpenGL context.", "Error",
|
||||
wxOK | wxICON_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
m_context.reset(ctx);
|
||||
}
|
||||
|
||||
wxGLContext * context() { return m_context.get(); }
|
||||
const wxGLContext * context() const { return m_context.get(); }
|
||||
};
|
||||
|
||||
// Tell the CSGDisplay how to swap buffers and set the gl context.
|
||||
class OCSGRenderer: public Renderer, public Slic3r::GL::CSGDisplay {
|
||||
public:
|
||||
|
||||
OCSGRenderer(wxGLCanvas *c): Renderer{c} {}
|
||||
|
||||
void set_active(long w, long h) override
|
||||
{
|
||||
m_canvas->SetCurrent(*m_context);
|
||||
Slic3r::GL::Display::set_active(w, h);
|
||||
}
|
||||
|
||||
void swap_buffers() override { m_canvas->SwapBuffers(); }
|
||||
};
|
||||
|
||||
// Tell the CSGDisplay how to swap buffers and set the gl context.
|
||||
class ShaderCSGRenderer : public Renderer, public Slic3r::GL::ShaderCSGDisplay {
|
||||
public:
|
||||
|
||||
ShaderCSGRenderer(wxGLCanvas *c): Renderer{c} {}
|
||||
|
||||
void set_active(long w, long h) override
|
||||
{
|
||||
m_canvas->SetCurrent(*m_context);
|
||||
Slic3r::GL::Display::set_active(w, h);
|
||||
}
|
||||
|
||||
void swap_buffers() override { m_canvas->SwapBuffers(); }
|
||||
};
|
||||
|
||||
// The opengl rendering facility. Here we implement the rendering objects.
|
||||
class Canvas: public wxGLCanvas
|
||||
{
|
||||
// One display is active at a time, the OCSGRenderer by default.
|
||||
std::shared_ptr<Slic3r::GL::Display> m_display;
|
||||
|
||||
public:
|
||||
|
||||
template<class...Args>
|
||||
Canvas(Args &&...args): wxGLCanvas(std::forward<Args>(args)...) {}
|
||||
|
||||
std::shared_ptr<Slic3r::GL::Display> get_display() const { return m_display; }
|
||||
|
||||
void set_display(std::shared_ptr<Slic3r::GL::Display> d) { m_display = d; }
|
||||
};
|
||||
|
||||
// Enumerate possible mouse events, we will record them.
|
||||
enum EEvents { LCLK_U, RCLK_U, LCLK_D, RCLK_D, DDCLK, SCRL, MV };
|
||||
struct Event
|
||||
{
|
||||
EEvents type;
|
||||
long a, b;
|
||||
Event(EEvents t, long x = 0, long y = 0) : type{t}, a{x}, b{y} {}
|
||||
};
|
||||
|
||||
// Create a special mouse input adapter, which can store (record) the received
|
||||
// mouse signals into a file and play back the stored events later.
|
||||
class RecorderMouseInput: public MouseInput {
|
||||
std::vector<Event> m_events;
|
||||
bool m_recording = false, m_playing = false;
|
||||
|
||||
public:
|
||||
void left_click_down() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(LCLK_D);
|
||||
if (!m_playing) MouseInput::left_click_down();
|
||||
}
|
||||
void left_click_up() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(LCLK_U);
|
||||
if (!m_playing) MouseInput::left_click_up();
|
||||
}
|
||||
void right_click_down() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(RCLK_D);
|
||||
if (!m_playing) MouseInput::right_click_down();
|
||||
}
|
||||
void right_click_up() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(RCLK_U);
|
||||
if (!m_playing) MouseInput::right_click_up();
|
||||
}
|
||||
void double_click() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(DDCLK);
|
||||
if (!m_playing) MouseInput::double_click();
|
||||
}
|
||||
void scroll(long v, long d, WheelAxis wa) override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(SCRL, v, d);
|
||||
if (!m_playing) MouseInput::scroll(v, d, wa);
|
||||
}
|
||||
void move_to(long x, long y) override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(MV, x, y);
|
||||
if (!m_playing) MouseInput::move_to(x, y);
|
||||
}
|
||||
|
||||
void save(std::ostream &stream)
|
||||
{
|
||||
for (const Event &evt : m_events)
|
||||
stream << evt.type << " " << evt.a << " " << evt.b << std::endl;
|
||||
}
|
||||
|
||||
void load(std::istream &stream)
|
||||
{
|
||||
m_events.clear();
|
||||
while (stream.good()) {
|
||||
int type; long a, b;
|
||||
stream >> type >> a >> b;
|
||||
m_events.emplace_back(EEvents(type), a, b);
|
||||
}
|
||||
}
|
||||
|
||||
void record(bool r) { m_recording = r; if (r) m_events.clear(); }
|
||||
|
||||
void play()
|
||||
{
|
||||
m_playing = true;
|
||||
for (const Event &evt : m_events) {
|
||||
switch (evt.type) {
|
||||
case LCLK_U: MouseInput::left_click_up(); break;
|
||||
case LCLK_D: MouseInput::left_click_down(); break;
|
||||
case RCLK_U: MouseInput::right_click_up(); break;
|
||||
case RCLK_D: MouseInput::right_click_down(); break;
|
||||
case DDCLK: MouseInput::double_click(); break;
|
||||
case SCRL: MouseInput::scroll(evt.a, evt.b, WheelAxis::waVertical); break;
|
||||
case MV: MouseInput::move_to(evt.a, evt.b); break;
|
||||
}
|
||||
|
||||
wxTheApp->Yield();
|
||||
if (!m_playing)
|
||||
break;
|
||||
}
|
||||
m_playing = false;
|
||||
}
|
||||
|
||||
void stop() { m_playing = false; }
|
||||
bool is_playing() const { return m_playing; }
|
||||
};
|
||||
|
||||
// The top level frame of the application.
|
||||
class MyFrame: public wxFrame
|
||||
{
|
||||
// Instantiate the 3D engine.
|
||||
std::shared_ptr<Scene> m_scene; // Model
|
||||
std::shared_ptr<Canvas> m_canvas; // Views store
|
||||
std::shared_ptr<OCSGRenderer> m_ocsgdisplay; // View
|
||||
std::shared_ptr<ShaderCSGRenderer> m_shadercsg_display; // Another view
|
||||
std::shared_ptr<Controller> m_ctl; // Controller
|
||||
|
||||
// Add a status bar with progress indication.
|
||||
std::shared_ptr<Slic3r::GUI::ProgressStatusBar> m_stbar;
|
||||
|
||||
RecorderMouseInput m_mouse;
|
||||
|
||||
// When loading a Model from 3mf and preparing it, we use a separate thread.
|
||||
class SLAJob: public Slic3r::GUI::Job {
|
||||
MyFrame *m_parent;
|
||||
std::unique_ptr<Slic3r::SLAPrint> m_print;
|
||||
std::string m_fname;
|
||||
|
||||
public:
|
||||
SLAJob(MyFrame *frame, const std::string &fname)
|
||||
: Slic3r::GUI::Job{frame->m_stbar}
|
||||
, m_parent{frame}
|
||||
, m_fname{fname}
|
||||
{}
|
||||
|
||||
// Runs in separate thread
|
||||
void process() override;
|
||||
|
||||
const std::string & get_project_fname() const { return m_fname; }
|
||||
|
||||
protected:
|
||||
|
||||
// Runs in the UI thread.
|
||||
void finalize() override
|
||||
{
|
||||
m_parent->m_scene->set_print(std::move(m_print));
|
||||
m_parent->m_stbar->set_status_text(
|
||||
wxString::Format("Model %s loaded.", m_fname));
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<SLAJob> m_ui_job;
|
||||
|
||||
// To keep track of the running average of measured fps values.
|
||||
double m_fps_avg = 0.;
|
||||
|
||||
// We need the record button across methods
|
||||
wxToggleButton *m_record_btn;
|
||||
wxComboBox * m_alg_select;
|
||||
wxComboBox * m_depth_select;
|
||||
wxComboBox * m_optimization_select;
|
||||
wxSpinCtrl * m_convexity_spin;
|
||||
wxToggleButton *m_csg_toggle;
|
||||
wxToggleButton *m_ms_toggle;
|
||||
wxStaticText *m_fpstext;
|
||||
|
||||
CSGSettings m_csg_settings;
|
||||
|
||||
void read_csg_settings(const wxCmdLineParser &parser);
|
||||
|
||||
void set_renderer_algorithm(const wxString &alg);
|
||||
|
||||
void activate_canvas_display();
|
||||
|
||||
public:
|
||||
MyFrame(const wxString & title,
|
||||
const wxPoint & pos,
|
||||
const wxSize & size,
|
||||
const wxCmdLineParser &parser);
|
||||
|
||||
// Grab a 3mf and load (hollow it out) within the UI job.
|
||||
void load_model(const std::string &fname) {
|
||||
m_ui_job = std::make_unique<SLAJob>(this, fname);
|
||||
m_ui_job->start();
|
||||
}
|
||||
|
||||
// Load a previously stored mouse event log and play it back.
|
||||
void play_back_mouse(const std::string &events_fname)
|
||||
{
|
||||
std::fstream stream(events_fname, std::fstream::in);
|
||||
|
||||
if (stream.good()) {
|
||||
std::string model_name;
|
||||
std::getline(stream, model_name);
|
||||
load_model(model_name);
|
||||
|
||||
while (!m_ui_job->is_finalized())
|
||||
wxTheApp->Yield();;
|
||||
|
||||
int w, h;
|
||||
stream >> w >> h;
|
||||
SetSize(w, h);
|
||||
|
||||
m_mouse.load(stream);
|
||||
if (m_record_btn) m_record_btn->Disable();
|
||||
m_mouse.play();
|
||||
}
|
||||
}
|
||||
|
||||
Canvas * canvas() { return m_canvas.get(); }
|
||||
const Canvas * canvas() const { return m_canvas.get(); }
|
||||
|
||||
// Bind the canvas mouse events to a class implementing MouseInput interface
|
||||
void bind_canvas_events(MouseInput &msinput);
|
||||
|
||||
double get_fps_average() const { return m_fps_avg; }
|
||||
};
|
||||
|
||||
// Possible OpenCSG configuration values. Will be used on the command line and
|
||||
// on the UI widgets.
|
||||
static const std::vector<wxString> CSG_ALGS = {"Auto", "Goldfeather", "SCS", "EnricoShader"};
|
||||
static const std::vector<wxString> CSG_DEPTH = {"Off", "OcclusionQuery", "On"};
|
||||
static const std::vector<wxString> CSG_OPT = { "Default", "ForceOn", "On", "Off" };
|
||||
|
||||
inline long get_idx(const wxString &a, const std::vector<wxString> &v)
|
||||
{
|
||||
auto it = std::find(v.begin(), v.end(), a.ToStdString());
|
||||
return it - v.begin();
|
||||
};
|
||||
|
||||
class App : public wxApp {
|
||||
MyFrame *m_frame = nullptr;
|
||||
wxString m_fname;
|
||||
public:
|
||||
bool OnInit() override {
|
||||
|
||||
wxCmdLineParser parser(argc, argv);
|
||||
|
||||
parser.AddOption("p", "play", "play back file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddOption("a", "algorithm", "OpenCSG algorithm [Auto|Goldfeather|SCS]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddOption("d", "depth", "OpenCSG depth strategy [Off|OcclusionQuery|On]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddOption("o", "optimization", "OpenCSG optimization strategy [Default|ForceOn|On|Off]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddOption("c", "convexity", "OpenCSG convexity parameter for generic meshes", wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddSwitch("", "disable-csg", "Disable csg rendering", wxCMD_LINE_PARAM_OPTIONAL);
|
||||
|
||||
parser.Parse();
|
||||
|
||||
bool is_play = parser.Found("play", &m_fname);
|
||||
|
||||
m_frame = new MyFrame("QIDIStudio OpenCSG Demo", wxDefaultPosition, wxSize(1024, 768), parser);
|
||||
|
||||
if (is_play) {
|
||||
Bind(wxEVT_IDLE, &App::Play, this);
|
||||
m_frame->Show( true );
|
||||
} else m_frame->Show( true );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Play(wxIdleEvent &) {
|
||||
Unbind(wxEVT_IDLE, &App::Play, this);
|
||||
m_frame->play_back_mouse(m_fname.ToStdString());
|
||||
m_frame->Destroy();
|
||||
}
|
||||
};
|
||||
|
||||
wxIMPLEMENT_APP(App);
|
||||
|
||||
void MyFrame::read_csg_settings(const wxCmdLineParser &parser)
|
||||
{
|
||||
wxString alg;
|
||||
parser.Found("algorithm", &alg);
|
||||
|
||||
wxString depth;
|
||||
parser.Found("depth", &depth);
|
||||
|
||||
wxString opt;
|
||||
parser.Found("optimization", &opt);
|
||||
|
||||
long convexity = 1;
|
||||
parser.Found("convexity", &convexity);
|
||||
|
||||
bool csg_off = parser.Found("disable-csg");
|
||||
|
||||
if (auto a = get_idx(alg, CSG_ALGS) < OpenCSG::AlgorithmUnused)
|
||||
m_csg_settings.set_algo(OpenCSG::Algorithm(a));
|
||||
|
||||
if (auto a = get_idx(depth, CSG_DEPTH) < OpenCSG::DepthComplexityAlgorithmUnused)
|
||||
m_csg_settings.set_depth_algo(OpenCSG::DepthComplexityAlgorithm(a));
|
||||
|
||||
if (auto a = get_idx(opt, CSG_OPT) < OpenCSG::OptimizationUnused)
|
||||
m_csg_settings.set_optimization(OpenCSG::Optimization(a));
|
||||
|
||||
m_csg_settings.set_convexity(unsigned(convexity));
|
||||
m_csg_settings.enable_csg(!csg_off);
|
||||
|
||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
}
|
||||
|
||||
void MyFrame::set_renderer_algorithm(const wxString &alg)
|
||||
{
|
||||
long alg_idx = get_idx(alg, CSG_ALGS);
|
||||
if (alg_idx < 0 || alg_idx >= long(CSG_ALGS.size())) return;
|
||||
|
||||
// If there is a valid display in place, save its camera.
|
||||
auto cam = m_canvas->get_display() ?
|
||||
m_canvas->get_display()->get_camera() : nullptr;
|
||||
|
||||
if (alg == "EnricoShader") {
|
||||
m_alg_select->SetSelection(int(alg_idx));
|
||||
m_depth_select->Disable();
|
||||
m_optimization_select->Disable();
|
||||
m_csg_toggle->Disable();
|
||||
|
||||
m_ocsgdisplay.reset();
|
||||
canvas()->set_display(nullptr);
|
||||
m_shadercsg_display = std::make_shared<ShaderCSGRenderer>(canvas());
|
||||
canvas()->set_display(m_shadercsg_display);
|
||||
} else {
|
||||
if (m_csg_settings.get_algo() > 0) m_depth_select->Enable(true);
|
||||
m_alg_select->SetSelection(m_csg_settings.get_algo());
|
||||
m_depth_select->SetSelection(m_csg_settings.get_depth_algo());
|
||||
m_optimization_select->SetSelection(m_csg_settings.get_optimization());
|
||||
m_convexity_spin->SetValue(int(m_csg_settings.get_convexity()));
|
||||
m_csg_toggle->SetValue(m_csg_settings.is_enabled());
|
||||
m_optimization_select->Enable();
|
||||
m_csg_toggle->Enable();
|
||||
|
||||
m_shadercsg_display.reset();
|
||||
canvas()->set_display(nullptr);
|
||||
m_ocsgdisplay = std::make_shared<OCSGRenderer>(canvas());
|
||||
m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
canvas()->set_display(m_ocsgdisplay);
|
||||
}
|
||||
|
||||
if (cam)
|
||||
m_canvas->get_display()->set_camera(cam);
|
||||
|
||||
m_ctl->remove_displays();
|
||||
m_ctl->add_display(m_canvas->get_display());
|
||||
m_canvas->get_display()->get_fps_counter().add_listener([this](double fps) {
|
||||
m_fpstext->SetLabel(wxString::Format("fps: %.2f", fps));
|
||||
m_fps_avg = 0.9 * m_fps_avg + 0.1 * fps;
|
||||
});
|
||||
|
||||
if (IsShown()) {
|
||||
activate_canvas_display();
|
||||
m_canvas->get_display()->on_scene_updated(*m_scene);
|
||||
}
|
||||
}
|
||||
|
||||
void MyFrame::activate_canvas_display()
|
||||
{
|
||||
const wxSize ClientSize = m_canvas->GetClientSize();
|
||||
m_canvas->get_display()->set_active(ClientSize.x, ClientSize.y);
|
||||
enable_multisampling(m_ms_toggle->GetValue());
|
||||
|
||||
m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent &) {
|
||||
// This is required even though dc is not used otherwise.
|
||||
wxPaintDC dc(m_canvas.get());
|
||||
const wxSize csize = m_canvas->GetClientSize();
|
||||
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
|
||||
m_canvas->get_display()->repaint();
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent &) {
|
||||
const wxSize csize = m_canvas->GetClientSize();
|
||||
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
|
||||
m_canvas->get_display()->repaint();
|
||||
});
|
||||
|
||||
// Do the repaint continuously
|
||||
m_canvas->Bind(wxEVT_IDLE, [this](wxIdleEvent &evt) {
|
||||
m_canvas->get_display()->repaint();
|
||||
evt.RequestMore();
|
||||
});
|
||||
|
||||
bind_canvas_events(m_mouse);
|
||||
}
|
||||
|
||||
MyFrame::MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size,
|
||||
const wxCmdLineParser &parser):
|
||||
wxFrame(nullptr, wxID_ANY, title, pos, size)
|
||||
{
|
||||
wxMenu *menuFile = new wxMenu;
|
||||
menuFile->Append(wxID_OPEN);
|
||||
menuFile->Append(wxID_EXIT);
|
||||
wxMenuBar *menuBar = new wxMenuBar;
|
||||
menuBar->Append( menuFile, "&File" );
|
||||
SetMenuBar( menuBar );
|
||||
|
||||
m_stbar = std::make_shared<Slic3r::GUI::ProgressStatusBar>(this);
|
||||
m_stbar->embed(this);
|
||||
|
||||
SetStatusText( "Welcome to wxWidgets!" );
|
||||
|
||||
int attribList[] =
|
||||
{WX_GL_RGBA, WX_GL_DOUBLEBUFFER,
|
||||
// RGB channels each should be allocated with 8 bit depth. One
|
||||
// should almost certainly get these bit depths by default.
|
||||
WX_GL_MIN_RED, 8, WX_GL_MIN_GREEN, 8, WX_GL_MIN_BLUE, 8,
|
||||
// Requesting an 8 bit alpha channel. Interestingly, the NVIDIA
|
||||
// drivers would most likely work with some alpha plane, but
|
||||
// glReadPixels would not return the alpha channel on NVIDIA if
|
||||
// not requested when the GL context is created.
|
||||
WX_GL_MIN_ALPHA, 8, WX_GL_DEPTH_SIZE, 8, WX_GL_STENCIL_SIZE, 8,
|
||||
WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0};
|
||||
|
||||
m_scene = std::make_shared<Scene>();
|
||||
m_ctl = std::make_shared<Controller>();
|
||||
m_ctl->set_scene(m_scene);
|
||||
|
||||
m_canvas = std::make_shared<Canvas>(this, wxID_ANY, attribList,
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE);
|
||||
|
||||
read_csg_settings(parser);
|
||||
|
||||
wxPanel *control_panel = new wxPanel(this);
|
||||
|
||||
auto controlsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto slider_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto console_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto slider = new wxSlider(control_panel, wxID_ANY, 0, 0, 100,
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
wxSL_VERTICAL);
|
||||
slider_sizer->Add(slider, 1, wxEXPAND);
|
||||
|
||||
m_ms_toggle = new wxToggleButton(control_panel, wxID_ANY, "Multisampling");
|
||||
console_sizer->Add(m_ms_toggle, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
m_csg_toggle = new wxToggleButton(control_panel, wxID_ANY, "CSG");
|
||||
m_csg_toggle->SetValue(true);
|
||||
console_sizer->Add(m_csg_toggle, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
auto add_combobox = [control_panel, console_sizer]
|
||||
(const wxString &label, const std::vector<wxString> &list)
|
||||
{
|
||||
auto widget = new wxComboBox(control_panel, wxID_ANY, list[0],
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
int(list.size()), list.data());
|
||||
|
||||
auto sz = new wxBoxSizer(wxHORIZONTAL);
|
||||
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
|
||||
wxALL | wxALIGN_CENTER, 5);
|
||||
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
|
||||
console_sizer->Add(sz, 0, wxEXPAND);
|
||||
return widget;
|
||||
};
|
||||
|
||||
auto add_spinctl = [control_panel, console_sizer]
|
||||
(const wxString &label, int initial, int min, int max)
|
||||
{
|
||||
auto widget = new wxSpinCtrl(
|
||||
control_panel, wxID_ANY,
|
||||
wxString::Format("%d", initial),
|
||||
wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max,
|
||||
initial);
|
||||
|
||||
auto sz = new wxBoxSizer(wxHORIZONTAL);
|
||||
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
|
||||
wxALL | wxALIGN_CENTER, 5);
|
||||
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
|
||||
console_sizer->Add(sz, 0, wxEXPAND);
|
||||
return widget;
|
||||
};
|
||||
|
||||
m_convexity_spin = add_spinctl("Convexity", CSGSettings::DEFAULT_CONVEXITY, 0, 100);
|
||||
|
||||
m_alg_select = add_combobox("Algorithm", CSG_ALGS);
|
||||
m_depth_select = add_combobox("Depth Complexity", CSG_DEPTH);
|
||||
m_optimization_select = add_combobox("Optimization", CSG_OPT);
|
||||
|
||||
m_fpstext = new wxStaticText(control_panel, wxID_ANY, "");
|
||||
console_sizer->Add(m_fpstext, 0, wxALL, 5);
|
||||
|
||||
m_record_btn = new wxToggleButton(control_panel, wxID_ANY, "Record");
|
||||
console_sizer->Add(m_record_btn, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
controlsizer->Add(slider_sizer, 0, wxEXPAND);
|
||||
controlsizer->Add(console_sizer, 1, wxEXPAND);
|
||||
|
||||
control_panel->SetSizer(controlsizer);
|
||||
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(m_canvas.get(), 1, wxEXPAND);
|
||||
sizer->Add(control_panel, 0, wxEXPAND);
|
||||
SetSizer(sizer);
|
||||
|
||||
wxString alg;
|
||||
if (!parser.Found("algorithm", &alg)) alg = "Auto";
|
||||
|
||||
set_renderer_algorithm(alg);
|
||||
|
||||
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt){
|
||||
if (m_canvas) RemoveChild(m_canvas.get());
|
||||
m_canvas.reset();
|
||||
if (!m_mouse.is_playing()) evt.Skip();
|
||||
else m_mouse.stop();
|
||||
});
|
||||
|
||||
Bind(wxEVT_MENU, [this](wxCommandEvent &) {
|
||||
wxFileDialog dlg(this, "Select project file", wxEmptyString,
|
||||
wxEmptyString, "*.3mf", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK) load_model(dlg.GetPath().ToStdString());
|
||||
}, wxID_OPEN);
|
||||
|
||||
Bind(wxEVT_MENU, [this](wxCommandEvent &) { Close(true); }, wxID_EXIT);
|
||||
|
||||
Bind(wxEVT_SHOW, [this](wxShowEvent &) {
|
||||
activate_canvas_display();
|
||||
});
|
||||
|
||||
Bind(wxEVT_SLIDER, [this, slider](wxCommandEvent &) {
|
||||
m_ctl->move_clip_plane(double(slider->GetValue()));
|
||||
});
|
||||
|
||||
m_ms_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
|
||||
enable_multisampling(m_ms_toggle->GetValue());
|
||||
m_canvas->get_display()->repaint();
|
||||
});
|
||||
|
||||
m_csg_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
|
||||
CSGSettings stt = m_ocsgdisplay->get_csgsettings();
|
||||
stt.enable_csg(m_csg_toggle->GetValue());
|
||||
m_ocsgdisplay->apply_csgsettings(stt);
|
||||
});
|
||||
|
||||
m_alg_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
wxString alg = m_alg_select->GetValue();
|
||||
int sel = m_alg_select->GetSelection();
|
||||
m_csg_settings.set_algo(sel);
|
||||
set_renderer_algorithm(alg);
|
||||
});
|
||||
|
||||
m_depth_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
int sel = m_depth_select->GetSelection();
|
||||
m_csg_settings.set_depth_algo(sel);
|
||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
});
|
||||
|
||||
m_optimization_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
int sel = m_optimization_select->GetSelection();
|
||||
m_csg_settings.set_optimization(sel);
|
||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
});
|
||||
|
||||
m_convexity_spin->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent &) {
|
||||
int c = m_convexity_spin->GetValue();
|
||||
if (c > 0) {
|
||||
m_csg_settings.set_convexity(unsigned(c));
|
||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
}
|
||||
});
|
||||
|
||||
m_record_btn->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &) {
|
||||
if (!m_ui_job) {
|
||||
m_stbar->set_status_text("No project loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_record_btn->GetValue()) {
|
||||
if (auto c = m_canvas->get_display()->get_camera()) reset(*c);
|
||||
m_ctl->on_scene_updated(*m_scene);
|
||||
m_mouse.record(true);
|
||||
} else {
|
||||
m_mouse.record(false);
|
||||
wxFileDialog dlg(this, "Select output file",
|
||||
wxEmptyString, wxEmptyString, "*.events",
|
||||
wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
std::fstream stream(dlg.GetPath().ToStdString(),
|
||||
std::fstream::out);
|
||||
|
||||
if (stream.good()) {
|
||||
stream << m_ui_job->get_project_fname() << "\n";
|
||||
wxSize winsize = GetSize();
|
||||
stream << winsize.x << " " << winsize.y << "\n";
|
||||
m_mouse.save(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MyFrame::bind_canvas_events(MouseInput &ms)
|
||||
{
|
||||
m_canvas->Bind(wxEVT_MOUSEWHEEL, [&ms](wxMouseEvent &evt) {
|
||||
ms.scroll(evt.GetWheelRotation(), evt.GetWheelDelta(),
|
||||
evt.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL ?
|
||||
Slic3r::GL::MouseInput::waVertical :
|
||||
Slic3r::GL::MouseInput::waHorizontal);
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_MOTION, [&ms](wxMouseEvent &evt) {
|
||||
ms.move_to(evt.GetPosition().x, evt.GetPosition().y);
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_RIGHT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
|
||||
ms.right_click_down();
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_RIGHT_UP, [&ms](wxMouseEvent & /*evt*/) {
|
||||
ms.right_click_up();
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_LEFT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
|
||||
ms.left_click_down();
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_LEFT_UP, [&ms](wxMouseEvent & /*evt*/) {
|
||||
ms.left_click_up();
|
||||
});
|
||||
|
||||
ms.add_listener(m_ctl);
|
||||
}
|
||||
|
||||
void MyFrame::SLAJob::process()
|
||||
{
|
||||
using SlStatus = Slic3r::PrintBase::SlicingStatus;
|
||||
|
||||
Slic3r::DynamicPrintConfig cfg;
|
||||
auto model = Slic3r::Model::read_from_file(m_fname, &cfg);
|
||||
|
||||
m_print = std::make_unique<Slic3r::SLAPrint>();
|
||||
m_print->apply(model, cfg);
|
||||
|
||||
Slic3r::PrintBase::TaskParams params;
|
||||
params.to_object_step = Slic3r::slaposHollowing;
|
||||
m_print->set_task(params);
|
||||
|
||||
m_print->set_status_callback([this](const SlStatus &status) {
|
||||
update_status(status.percent, status.text);
|
||||
});
|
||||
|
||||
try {
|
||||
m_print->process();
|
||||
} catch(std::exception &e) {
|
||||
update_status(0, wxString("Exception during processing: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
//int main() {}
|
||||
7
sandboxes/openvdb/CMakeLists.txt
Normal file
7
sandboxes/openvdb/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
if(TARGET OpenVDB::openvdb)
|
||||
add_executable(openvdb_example openvdb_example.cpp)
|
||||
|
||||
target_link_libraries(openvdb_example libslic3r)
|
||||
target_link_libraries(openvdb_example OpenVDB::openvdb)
|
||||
endif()
|
||||
|
||||
37
sandboxes/openvdb/openvdb_example.cpp
Normal file
37
sandboxes/openvdb/openvdb_example.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <openvdb/openvdb.h>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
// Initialize the OpenVDB library. This must be called at least
|
||||
// once per program and may safely be called multiple times.
|
||||
openvdb::initialize();
|
||||
// Create an empty floating-point grid with background value 0.
|
||||
openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create();
|
||||
std::cout << "Testing random access:" << std::endl;
|
||||
// Get an accessor for coordinate-based access to voxels.
|
||||
openvdb::FloatGrid::Accessor accessor = grid->getAccessor();
|
||||
// Define a coordinate with large signed indices.
|
||||
openvdb::Coord xyz(1000, -200000000, 30000000);
|
||||
// Set the voxel value at (1000, -200000000, 30000000) to 1.
|
||||
accessor.setValue(xyz, 1.0);
|
||||
// Verify that the voxel value at (1000, -200000000, 30000000) is 1.
|
||||
std::cout << "Grid" << xyz << " = " << accessor.getValue(xyz) << std::endl;
|
||||
// Reset the coordinates to those of a different voxel.
|
||||
xyz.reset(1000, 200000000, -30000000);
|
||||
// Verify that the voxel value at (1000, 200000000, -30000000) is
|
||||
// the background value, 0.
|
||||
std::cout << "Grid" << xyz << " = " << accessor.getValue(xyz) << std::endl;
|
||||
// Set the voxel value at (1000, 200000000, -30000000) to 2.
|
||||
accessor.setValue(xyz, 2.0);
|
||||
// Set the voxels at the two extremes of the available coordinate space.
|
||||
// For 32-bit signed coordinates these are (-2147483648, -2147483648, -2147483648)
|
||||
// and (2147483647, 2147483647, 2147483647).
|
||||
accessor.setValue(openvdb::Coord::min(), 3.0f);
|
||||
accessor.setValue(openvdb::Coord::max(), 4.0f);
|
||||
std::cout << "Testing sequential access:" << std::endl;
|
||||
// Print all active ("on") voxels by means of an iterator.
|
||||
for (openvdb::FloatGrid::ValueOnCIter iter = grid->cbeginValueOn(); iter; ++iter) {
|
||||
std::cout << "Grid" << iter.getCoord() << " = " << *iter << std::endl;
|
||||
}
|
||||
}
|
||||
2
sandboxes/slasupporttree/CMakeLists.txt
Normal file
2
sandboxes/slasupporttree/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
add_executable(slasupporttree slasupporttree.cpp)
|
||||
target_link_libraries(slasupporttree libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_DL_LIBS})
|
||||
42
sandboxes/slasupporttree/slasupporttree.cpp
Normal file
42
sandboxes/slasupporttree/slasupporttree.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <libslic3r/libslic3r.h>
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/Tesselate.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/SLA/SLAAutoSupports.hpp>
|
||||
#include <libslic3r/SLA/SLASupportTree.hpp>
|
||||
#include <libslic3r/SLAPrint.hpp>
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/mutex.h>
|
||||
#include <future>
|
||||
|
||||
const std::string USAGE_STR = {
|
||||
"Usage: slasupporttree stlfilename.stl"
|
||||
};
|
||||
|
||||
int main(const int argc, const char *argv[]) {
|
||||
using namespace Slic3r;
|
||||
using std::cout; using std::endl;
|
||||
|
||||
if(argc < 2) {
|
||||
cout << USAGE_STR << endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
DynamicPrintConfig config;
|
||||
|
||||
Model model = Model::read_from_file(argv[1], &config);
|
||||
|
||||
SLAPrint print;
|
||||
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user