PrusaSlicer-NonPlainar/sandboxes/opencsg/Engine.cpp
Vojtech Bubnik 8a2a9dba2f Eradicated admesh from TriangleMesh:
TriangleMesh newly only holds indexed_triangle_set and
TriangleMeshStats. TriangleMeshStats contains an excerpt of stl_stats.
TriangleMeshStats are updated when initializing with indexed_triangle_set.

Admesh triangle mesh fixing is newly only used when loading an STL.
AMF / 3MF / OBJ file formats are already indexed triangle sets, thus
they are no more converted to admesh stl_file format, nor fixed
through admesh repair machinery. When importing AMF / 3MF / OBJ files,
volume is calculated and if negative, all faces are flipped. Also
a bounding box and number of open edges is calculated.

Implemented its_number_of_patches(), its_num_open_edges()
Optimized its_split(), its_is_splittable() using a visitor pattern.

Reworked QHull integration into TriangleMesh:
    1) Face normals were not right.
    2) Indexed triangle set is newly emitted instead of duplicating
       vertices for each face.

Fixed cut_mesh(): Orient the triangulated faces correctly.
2021-09-20 17:12:22 +02:00

496 lines
16 KiB
C++

#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(uqptr<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();
}
shptr<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;
}
shptr<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