1 #ifndef SLIC3R_OCSG_EXMP_ENGINE_HPP
2 #define SLIC3R_OCSG_EXMP_ENGINE_HPP
3
4 #include <vector>
5 #include <memory>
6 #include <chrono>
7
8 #include <libslic3r/Geometry.hpp>
9 #include <libslic3r/Model.hpp>
10 #include <libslic3r/TriangleMesh.hpp>
11 #include <libslic3r/SLA/Hollowing.hpp>
12 #include <opencsg/opencsg.h>
13
14 namespace Slic3r {
15
16 class SLAPrint;
17
18 namespace GL {
19
20 // Simple shorthands for smart pointers
21 template<class T> using shptr = std::shared_ptr<T>;
22 template<class T> using uqptr = std::unique_ptr<T>;
23 template<class T> using wkptr = std::weak_ptr<T>;
24
25 template<class T, class A = std::allocator<T>> using vector = std::vector<T, A>;
26
27 // remove empty weak pointers from a vector
cleanup(vector<std::weak_ptr<L>> & listeners)28 template<class L> inline void cleanup(vector<std::weak_ptr<L>> &listeners) {
29 auto it = std::remove_if(listeners.begin(), listeners.end(),
30 [](auto &l) { return !l.lock(); });
31 listeners.erase(it, listeners.end());
32 }
33
34 // Call a class method on each element of a vector of objects (weak pointers)
35 // of the same type.
36 template<class F, class L, class...Args>
call(F && f,vector<std::weak_ptr<L>> & listeners,Args &&...args)37 inline void call(F &&f, vector<std::weak_ptr<L>> &listeners, Args&&... args) {
38 for (auto &l : listeners)
39 if (auto p = l.lock()) ((p.get())->*f)(std::forward<Args>(args)...);
40 }
41
42 // A representation of a mouse input for the engine.
43 class MouseInput
44 {
45 public:
46 enum WheelAxis { waVertical, waHorizontal };
47
48 // Interface to implement if an object wants to receive notifications
49 // about mouse events.
50 class Listener {
51 public:
52 virtual ~Listener();
53
on_left_click_down()54 virtual void on_left_click_down() {}
on_left_click_up()55 virtual void on_left_click_up() {}
on_right_click_down()56 virtual void on_right_click_down() {}
on_right_click_up()57 virtual void on_right_click_up() {}
on_double_click()58 virtual void on_double_click() {}
on_scroll(long,long,WheelAxis)59 virtual void on_scroll(long /*v*/, long /*delta*/, WheelAxis ) {}
on_moved_to(long,long)60 virtual void on_moved_to(long /*x*/, long /*y*/) {}
61 };
62
63 private:
64 vector<wkptr<Listener>> m_listeners;
65
66 public:
67 virtual ~MouseInput() = default;
68
left_click_down()69 virtual void left_click_down()
70 {
71 call(&Listener::on_left_click_down, m_listeners);
72 }
left_click_up()73 virtual void left_click_up()
74 {
75 call(&Listener::on_left_click_up, m_listeners);
76 }
right_click_down()77 virtual void right_click_down()
78 {
79 call(&Listener::on_right_click_down, m_listeners);
80 }
right_click_up()81 virtual void right_click_up()
82 {
83 call(&Listener::on_right_click_up, m_listeners);
84 }
double_click()85 virtual void double_click()
86 {
87 call(&Listener::on_double_click, m_listeners);
88 }
scroll(long v,long d,WheelAxis wa)89 virtual void scroll(long v, long d, WheelAxis wa)
90 {
91 call(&Listener::on_scroll, m_listeners, v, d, wa);
92 }
move_to(long x,long y)93 virtual void move_to(long x, long y)
94 {
95 call(&Listener::on_moved_to, m_listeners, x, y);
96 }
97
add_listener(shptr<Listener> listener)98 void add_listener(shptr<Listener> listener)
99 {
100 m_listeners.emplace_back(listener);
101 cleanup(m_listeners);
102 }
103 };
104
105 // This is a stripped down version of Slic3r::IndexedVertexArray
106 class IndexedVertexArray {
107 public:
~IndexedVertexArray()108 ~IndexedVertexArray() { release_geometry(); }
109
110 // Vertices and their normals, interleaved to be used by void
111 // glInterleavedArrays(GL_N3F_V3F, 0, x)
112 vector<float> vertices_and_normals_interleaved;
113 vector<int> triangle_indices;
114 vector<int> quad_indices;
115
116 // When the geometry data is loaded into the graphics card as Vertex
117 // Buffer Objects, the above mentioned std::vectors are cleared and the
118 // following variables keep their original length.
119 size_t vertices_and_normals_interleaved_size{ 0 };
120 size_t triangle_indices_size{ 0 };
121 size_t quad_indices_size{ 0 };
122
123 // IDs of the Vertex Array Objects, into which the geometry has been loaded.
124 // Zero if the VBOs are not sent to GPU yet.
125 unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
126 unsigned int triangle_indices_VBO_id{ 0 };
127 unsigned int quad_indices_VBO_id{ 0 };
128
129
130 void push_geometry(float x, float y, float z, float nx, float ny, float nz);
131
push_geometry(double x,double y,double z,double nx,double ny,double nz)132 inline void push_geometry(
133 double x, double y, double z, double nx, double ny, double nz)
134 {
135 push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
136 }
137
push_geometry(const Vec3d & p,const Vec3d & n)138 inline void push_geometry(const Vec3d &p, const Vec3d &n)
139 {
140 push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
141 }
142
143 void push_triangle(int idx1, int idx2, int idx3);
144
145 void load_mesh(const TriangleMesh &mesh);
146
has_VBOs() const147 inline bool has_VBOs() const
148 {
149 return vertices_and_normals_interleaved_VBO_id != 0;
150 }
151
152 // Finalize the initialization of the geometry & indices,
153 // upload the geometry and indices to OpenGL VBO objects
154 // and shrink the allocated data, possibly relasing it if it has been
155 // loaded into the VBOs.
156 void finalize_geometry();
157 // Release the geometry data, release OpenGL VBOs.
158 void release_geometry();
159
160 void render() const;
161
162 // Is there any geometry data stored?
empty() const163 bool empty() const { return vertices_and_normals_interleaved_size == 0; }
164
165 void clear();
166
167 // Shrink the internal storage to tighly fit the data stored.
168 void shrink_to_fit();
169 };
170
171 // Try to enable or disable multisampling.
172 bool enable_multisampling(bool e = true);
173
174 class Volume {
175 IndexedVertexArray m_geom;
176 Geometry::Transformation m_trafo;
177
178 public:
179
180 void render();
181
translation(const Vec3d & offset)182 void translation(const Vec3d &offset) { m_trafo.set_offset(offset); }
rotation(const Vec3d & rot)183 void rotation(const Vec3d &rot) { m_trafo.set_rotation(rot); }
scale(const Vec3d & scaleing)184 void scale(const Vec3d &scaleing) { m_trafo.set_scaling_factor(scaleing); }
scale(double s)185 void scale(double s) { scale({s, s, s}); }
186
load_mesh(const TriangleMesh & mesh)187 inline void load_mesh(const TriangleMesh &mesh)
188 {
189 m_geom.load_mesh(mesh);
190 m_geom.finalize_geometry();
191 }
192 };
193
194 // A primitive that can be used with OpenCSG rendering algorithms.
195 // Does a similar job to GLVolume.
196 class Primitive : public Volume, public OpenCSG::Primitive
197 {
198 public:
199 using OpenCSG::Primitive::Primitive;
200
Primitive()201 Primitive() : OpenCSG::Primitive(OpenCSG::Intersection, 1) {}
202
render()203 void render() override { Volume::render(); }
204 };
205
206 // A simple representation of a camera in a 3D scene
207 class Camera {
208 protected:
209 Vec2f m_rot = {0., 0.};
210 Vec3d m_referene = {0., 0., 0.};
211 double m_zoom = 0.;
212 double m_clip_z = 0.;
213 public:
214
215 virtual ~Camera() = default;
216
217 virtual void view();
218 virtual void set_screen(long width, long height) = 0;
219
set_rotation(const Vec2f & rotation)220 void set_rotation(const Vec2f &rotation) { m_rot = rotation; }
rotate(const Vec2f & rotation)221 void rotate(const Vec2f &rotation) { m_rot += rotation; }
set_zoom(double z)222 void set_zoom(double z) { m_zoom = z; }
set_reference_point(const Vec3d & p)223 void set_reference_point(const Vec3d &p) { m_referene = p; }
set_clip_z(double z)224 void set_clip_z(double z) { m_clip_z = z; }
225 };
226
227 // Reset a camera object
reset(Camera & cam)228 inline void reset(Camera &cam)
229 {
230 cam.set_rotation({0., 0.});
231 cam.set_zoom(0.);
232 cam.set_reference_point({0., 0., 0.});
233 cam.set_clip_z(0.);
234 }
235
236 // Specialization of a camera which shows in perspective projection
237 class PerspectiveCamera: public Camera {
238 public:
239
240 void set_screen(long width, long height) override;
241 };
242
243 // A simple counter of FPS. Subscribed objects will receive updates of the
244 // current fps.
245 class FpsCounter {
246 vector<std::function<void(double)>> m_listeners;
247
248 using Clock = std::chrono::high_resolution_clock;
249 using Duration = Clock::duration;
250 using TimePoint = Clock::time_point;
251
252 int m_frames = 0;
253 TimePoint m_last = Clock::now(), m_window = m_last;
254
255 double m_resolution = 0.1, m_window_size = 1.0;
256 double m_fps = 0.;
257
to_sec(Duration d)258 static double to_sec(Duration d)
259 {
260 return d.count() * double(Duration::period::num) / Duration::period::den;
261 }
262
263 public:
264
265 void update();
266
add_listener(std::function<void (double)> lst)267 void add_listener(std::function<void(double)> lst)
268 {
269 m_listeners.emplace_back(lst);
270 }
271
clear_listeners()272 void clear_listeners() { m_listeners = {}; }
273
274 void set_notification_interval(double seconds);
275 void set_measure_window_size(double seconds);
276
get_notification_interval() const277 double get_notification_interval() const { return m_resolution; }
get_mesure_window_size() const278 double get_mesure_window_size() const { return m_window_size; }
279 };
280
281 // Collection of the used OpenCSG library settings.
282 class CSGSettings {
283 public:
284 static const constexpr unsigned DEFAULT_CONVEXITY = 10;
285
286 private:
287 OpenCSG::Algorithm m_csgalg = OpenCSG::Algorithm::Automatic;
288 OpenCSG::DepthComplexityAlgorithm m_depth_algo = OpenCSG::NoDepthComplexitySampling;
289 OpenCSG::Optimization m_optim = OpenCSG::OptimizationDefault;
290 bool m_enable = true;
291 unsigned int m_convexity = DEFAULT_CONVEXITY;
292
293 public:
get_algo() const294 int get_algo() const { return int(m_csgalg); }
set_algo(int alg)295 void set_algo(int alg)
296 {
297 if (alg < OpenCSG::Algorithm::AlgorithmUnused)
298 m_csgalg = OpenCSG::Algorithm(alg);
299 }
300
get_depth_algo() const301 int get_depth_algo() const { return int(m_depth_algo); }
set_depth_algo(int alg)302 void set_depth_algo(int alg)
303 {
304 if (alg < OpenCSG::DepthComplexityAlgorithmUnused)
305 m_depth_algo = OpenCSG::DepthComplexityAlgorithm(alg);
306 }
307
get_optimization() const308 int get_optimization() const { return int(m_optim); }
set_optimization(int o)309 void set_optimization(int o)
310 {
311 if (o < OpenCSG::Optimization::OptimizationUnused)
312 m_optim = OpenCSG::Optimization(o);
313 }
314
enable_csg(bool en=true)315 void enable_csg(bool en = true) { m_enable = en; }
is_enabled() const316 bool is_enabled() const { return m_enable; }
317
get_convexity() const318 unsigned get_convexity() const { return m_convexity; }
set_convexity(unsigned c)319 void set_convexity(unsigned c) { m_convexity = c; }
320 };
321
322 // The scene is a wrapper around SLAPrint which holds the data to be visualized.
323 class Scene
324 {
325 uqptr<SLAPrint> m_print;
326 public:
327
328 // Subscribers will be notified if the model is changed. This might be a
329 // display which will have to load the meshes and repaint itself when
330 // the scene data changes.
331 // eg. We load a new 3mf through the UI, this will notify the controller
332 // associated with the scene and all the displays that the controller is
333 // connected with.
334 class Listener {
335 public:
336 virtual ~Listener() = default;
337 virtual void on_scene_updated(const Scene &scene) = 0;
338 };
339
340 Scene();
341 ~Scene();
342
343 void set_print(uqptr<SLAPrint> &&print);
get_print() const344 const SLAPrint * get_print() const { return m_print.get(); }
345
346 BoundingBoxf3 get_bounding_box() const;
347
add_listener(shptr<Listener> listener)348 void add_listener(shptr<Listener> listener)
349 {
350 m_listeners.emplace_back(listener);
351 cleanup(m_listeners);
352 }
353
354 private:
355 vector<wkptr<Listener>> m_listeners;
356 };
357
358 // The basic Display. This is almost just an interface but will do all the
359 // initialization and show the fps values. Overriding the render_scene is
360 // needed to show the scene content. The specific method of displaying the
361 // scene is up the the particular implementation (OpenCSG or other screen space
362 // boolean algorithms)
363 class Display : public Scene::Listener
364 {
365 protected:
366 Vec2i m_size;
367 bool m_initialized = false;
368
369 shptr<Camera> m_camera;
370 FpsCounter m_fps_counter;
371
372 public:
373
Display(shptr<Camera> camera=nullptr)374 explicit Display(shptr<Camera> camera = nullptr)
375 : m_camera(camera ? camera : std::make_shared<PerspectiveCamera>())
376 {}
377
378 ~Display() override;
379
get_camera() const380 shptr<const Camera> get_camera() const { return m_camera; }
get_camera()381 shptr<Camera> get_camera() { return m_camera; }
set_camera(shptr<Camera> cam)382 void set_camera(shptr<Camera> cam) { m_camera = cam; }
383
384 virtual void swap_buffers() = 0;
385 virtual void set_active(long width, long height);
386 virtual void set_screen_size(long width, long height);
get_screen_size() const387 Vec2i get_screen_size() const { return m_size; }
388
389 virtual void repaint();
390
is_initialized() const391 bool is_initialized() const { return m_initialized; }
392
393 virtual void clear_screen();
render_scene()394 virtual void render_scene() {}
395
set_fps_counter(_FpsCounter && fpsc)396 template<class _FpsCounter> void set_fps_counter(_FpsCounter &&fpsc)
397 {
398 m_fps_counter = std::forward<_FpsCounter>(fpsc);
399 }
400
get_fps_counter() const401 const FpsCounter &get_fps_counter() const { return m_fps_counter; }
get_fps_counter()402 FpsCounter &get_fps_counter() { return m_fps_counter; }
403 };
404
405 // Special dispaly using OpenCSG for rendering the scene.
406 class CSGDisplay : public Display {
407 protected:
408 CSGSettings m_csgsettings;
409
410 // Cache the renderable primitives. These will be fetched when the scene
411 // is modified.
412 struct SceneCache {
413 vector<shptr<Primitive>> primitives;
414 vector<Primitive *> primitives_free;
415 vector<OpenCSG::Primitive *> primitives_csg;
416
417 void clear();
418
419 shptr<Primitive> add_mesh(const TriangleMesh &mesh);
420 shptr<Primitive> add_mesh(const TriangleMesh &mesh,
421 OpenCSG::Operation op,
422 unsigned covexity);
423 } m_scene_cache;
424
425 public:
426
427 // Receive or apply the new settings.
get_csgsettings() const428 const CSGSettings & get_csgsettings() const { return m_csgsettings; }
429 void apply_csgsettings(const CSGSettings &settings);
430
431 void render_scene() override;
432
433 void on_scene_updated(const Scene &scene) override;
434 };
435
436
437 // The controller is a hub which dispatches mouse events to the connected
438 // displays. It keeps track of the mouse wheel position, the states whether
439 // the mouse is being held, dragged, etc... All the connected displays will
440 // mirror the camera movement (if there is more than one display).
441 class Controller : public std::enable_shared_from_this<Controller>,
442 public MouseInput::Listener,
443 public Scene::Listener
444 {
445 long m_wheel_pos = 0;
446 Vec2i m_mouse_pos, m_mouse_pos_rprev, m_mouse_pos_lprev;
447 bool m_left_btn = false, m_right_btn = false;
448
449 shptr<Scene> m_scene;
450 vector<wkptr<Display>> m_displays;
451
452 // Call a method of Camera on all the cameras of the attached displays
453 template<class F, class...Args>
call_cameras(F && f,Args &&...args)454 void call_cameras(F &&f, Args&&... args) {
455 for (wkptr<Display> &l : m_displays)
456 if (auto disp = l.lock()) if (auto cam = disp->get_camera())
457 (cam.get()->*f)(std::forward<Args>(args)...);
458 }
459
460 public:
461
462 // Set the scene that will be controlled.
set_scene(shptr<Scene> scene)463 void set_scene(shptr<Scene> scene)
464 {
465 m_scene = scene;
466 m_scene->add_listener(shared_from_this());
467 }
468
get_scene() const469 const Scene * get_scene() const { return m_scene.get(); }
470
add_display(shptr<Display> disp)471 void add_display(shptr<Display> disp)
472 {
473 m_displays.emplace_back(disp);
474 cleanup(m_displays);
475 }
476
remove_displays()477 void remove_displays() { m_displays = {}; }
478
479 void on_scene_updated(const Scene &scene) override;
480
on_left_click_down()481 void on_left_click_down() override { m_left_btn = true; }
on_left_click_up()482 void on_left_click_up() override { m_left_btn = false; }
on_right_click_down()483 void on_right_click_down() override { m_right_btn = true; }
on_right_click_up()484 void on_right_click_up() override { m_right_btn = false; }
485
486 void on_scroll(long v, long d, MouseInput::WheelAxis wa) override;
487 void on_moved_to(long x, long y) override;
488
move_clip_plane(double z)489 void move_clip_plane(double z) { call_cameras(&Camera::set_clip_z, z); }
490 };
491
492 }} // namespace Slic3r::GL
493 #endif // SLIC3R_OCSG_EXMP_ENGINE_HPP
494