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