MRPT  1.9.9
COpenGLViewport.h
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | https://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2020, Individual contributors, see AUTHORS file |
6  | See: https://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See: https://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 #pragma once
10 
12 #include <mrpt/img/CImage.h>
13 #include <mrpt/opengl/CCamera.h>
18 #include <mrpt/opengl/Shader.h>
24 #include <mrpt/system/mrptEvent.h>
25 #include <map>
26 
27 namespace mrpt::img
28 {
29 class CImage;
30 }
31 namespace mrpt::opengl
32 {
33 /** A viewport within a COpenGLScene, containing a set of OpenGL objects to
34  *render.
35  * This class has protected constuctor, thus it cannot be created by users.
36  *Use COpenGLScene::createViewport instead.
37  * A viewport has these "operation modes":
38  * - Normal (default): It renders the contained objects.
39  * - Cloned: It clones the objects from another viewport. See \a
40  *setCloneView()
41  * - Image mode: It renders an image (e.g. from a video stream) efficiently
42  *using a textued quad. See \a setImageView().
43  *
44  * In any case, the viewport can be resized to only fit a part of the entire
45  *parent viewport.
46  * There will be always at least one viewport in a COpenGLScene named "main".
47  *
48  * This class can be observed (see mrpt::system::CObserver) for the following
49  *events (see mrpt::system::mrptEvent):
50  * - mrpt::opengl::mrptEventGLPreRender
51  * - mrpt::opengl::mrptEventGLPostRender
52  *
53  * Two directional light sources at infinity are created by default, with
54  *directions (-1,-1,-1) and (1,2,1), respectively.
55  *
56  * Lighting parameters are accessible via lightParameters().
57  *
58  * Refer to mrpt::opengl::COpenGLScene for further details.
59  * \ingroup mrpt_opengl_grp
60  */
64 {
66  friend class COpenGLScene;
67 
68  public:
69  /** @name Set the viewport "modes"
70  @{ */
71 
72  /** Set this viewport as a clone of some other viewport, given its name - as
73  * a side effect, current list of internal OpenGL objects is cleared.
74  * By default, only the objects are cloned, not the camera. See
75  * \sa resetCloneView
76  */
77  void setCloneView(const std::string& clonedViewport);
78 
79  /** Set this viewport into "image view"-mode, where an image is efficiently
80  * drawn (fitting the viewport area) using an OpenGL textured quad.
81  * Call this method with the new image to update the displayed image (but
82  * recall to first lock the parent openglscene's critical section, then do
83  * the update, then release the lock, and then issue a window repaint).
84  * Internally, the texture is drawn using a mrpt::opengl::CTexturedPlane
85  * The viewport can be reverted to behave like a normal viewport by
86  * calling setNormalMode()
87  */
88  void setImageView(const mrpt::img::CImage& img);
89 
90  /** Just like \a setImageView but moves the internal image memory instead of
91  * making a copy, so it's faster but empties the input image.
92  * \sa setImageView
93  */
94  void setImageView(mrpt::img::CImage&& img);
95 
96  /** Reset the viewport to normal mode: rendering its own objects.
97  * \sa setCloneView, setNormalMode
98  */
99  inline void resetCloneView() { setNormalMode(); }
100  /** If set to true, and setCloneView() has been called, this viewport will
101  * be rendered using the camera of the cloned viewport.
102  */
103  inline void setCloneCamera(bool enable) { m_isClonedCamera = enable; }
104  /** Resets the viewport to a normal 3D viewport \sa setCloneView,
105  * setImageView */
106  void setNormalMode();
107 
108  /** @} */
109  // end of Set the "viewport mode"
110 
111  /** @name OpenGL global settings that affect rendering all objects in the
112  scene/viewport
113  @{ */
114 
115  /** Sets glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST) is enabled, or GL_FASTEST
116  * otherwise. */
117  void enablePolygonNicest(bool enable = true)
118  {
120  }
122 
123  const TLightParameters& lightParameters() const { return m_lights; }
125 
126  /** @} */
127 
128  /** @name Change or read viewport properties (except "viewport modes")
129  @{ */
130 
131  /** Returns the name of the viewport */
132  inline std::string getName() { return m_name; }
133  /** Change the viewport position and dimension on the rendering window.
134  * X & Y coordinates here can have two interpretations:
135  * - If in the range [0,1], they are factors with respect to the actual
136  *window sizes (i.e. width=1 means the entire width of the rendering
137  *window).
138  * - If >1, they are interpreted as pixels.
139  *
140  * width & height can be interpreted as:
141  * - If >1, they are the size of the viewport in that dimension, in
142  *pixels.
143  * - If in [0,1], they are the size of the viewport in that dimension,
144  *in
145  *a factor of the width/height.
146  * - If in [-1,0[, the size is computed such as the right/top border
147  *ends
148  *up in the given coordinate, interpreted as a factor (e.g. -1: up to the
149  *end of the viewport, -0.5: up to the middle of it).
150  * - If <-1 the size is computed such as the right/top border ends up
151  *in
152  *the given absolute coordinate (e.g. -200: up to the row/column 200px).
153  *
154  * \note (x,y) specify the lower left corner of the viewport rectangle.
155  * \sa getViewportPosition
156  */
157  void setViewportPosition(
158  const double x, const double y, const double width,
159  const double height);
160 
161  /** Get the current viewport position and dimension on the rendering window.
162  * X & Y coordinates here can have two interpretations:
163  * - If in the range [0,1], they are factors with respect to the actual
164  * window sizes (i.e. width=1 means the entire width of the rendering
165  * window).
166  * - If >1, they are interpreted as pixels.
167  * \note (x,y) specify the lower left corner of the viewport rectangle.
168  * \sa setViewportPosition
169  */
170  void getViewportPosition(
171  double& x, double& y, double& width, double& height);
172 
173  /** Set the min/max clip depth distances of the rendering frustum (default:
174  * 0.1 - 10000)
175  * \sa getViewportClipDistances
176  */
177  void setViewportClipDistances(const float clip_min, const float clip_max);
178 
179  /** Get the current min/max clip depth distances of the rendering frustum
180  * (default: 0.1 - 10000)
181  * \sa setViewportClipDistances
182  */
183  void getViewportClipDistances(float& clip_min, float& clip_max) const;
184 
185  /** Set the border size ("frame") of the viewport (default=0) */
186  inline void setBorderSize(unsigned int lineWidth)
187  {
188  m_borderWidth = lineWidth;
189  }
190  inline unsigned int getBorderSize() const { return m_borderWidth; }
191 
193  const mrpt::img::TColor& getBorderColor() const { return m_borderColor; }
194 
195  /** Return whether the viewport will be rendered transparent over previous
196  * viewports.
197  */
198  inline bool isTransparent() { return m_isTransparent; }
199  /** Set the transparency, that is, whether the viewport will be rendered
200  * transparent over previous viewports (default=false).
201  */
202  inline void setTransparent(bool trans) { m_isTransparent = trans; }
203  /** Set a background color different from that of the parent GUI window */
205  {
206  m_custom_backgb_color = true;
207  m_background_color = color;
208  }
209 
211  {
212  return m_background_color;
213  }
214 
215  /** Compute the 3D ray corresponding to a given pixel; this can be used to
216  * allow the user to pick and select 3D objects by clicking onto the 2D
217  * image.
218  * \param x_coord Horizontal coordinate with the usual meaning (0:left of
219  * the viewport, W-1: right border).
220  * \param y_coord Horizontal coordinate with the usual meaning (0:top of
221  * the viewport, H-1: right border).
222  * \param out_cameraPose If not nullptr, will have the camera 3D pose as a
223  * mrpt::poses::CPose3D. See also
224  * \note (x,y) refer to VIEWPORT coordinates. Take into account this when
225  * viewports do not extend to the whole window size.
226  * \note x and y are double instead of integers to allow sub-pixel
227  * precision.
228  * \sa getCurrentCameraPose
229  */
231  const double x_coord, const double y_coord,
232  mrpt::math::TLine3D& out_ray,
233  mrpt::poses::CPose3D* out_cameraPose = nullptr) const;
234 
235  /** @} */ // end of Change or read viewport properties
236 
237  /** @name Contained objects set/get/search
238  @{ */
239 
240  using const_iterator = CListOpenGLObjects::const_iterator;
241  using iterator = CListOpenGLObjects::iterator;
242 
243  inline const_iterator begin() const { return m_objects.begin(); }
244  inline const_iterator end() const { return m_objects.end(); }
245  inline iterator begin() { return m_objects.begin(); }
246  inline iterator end() { return m_objects.end(); }
247  /** Delete all internal obejcts
248  * \sa insert */
249  void clear();
250 
251  /** Insert a new object into the list.
252  * The object MUST NOT be deleted, it will be deleted automatically by
253  * this object when not required anymore.
254  */
255  void insert(const CRenderizable::Ptr& newObject);
256 
257  /** Compute the current 3D camera pose.
258  * \sa get3DRayForPixelCoord
259  */
260  void getCurrentCameraPose(mrpt::poses::CPose3D& out_cameraPose) const;
261 
262  /** Changes the point of view of the camera, from a given pose.
263  * \sa getCurrentCameraPose
264  */
266 
267  /** Returns the first object with a given name, or nullptr if not found.
268  */
269  CRenderizable::Ptr getByName(const std::string& str);
270 
271  /** Returns the i'th object of a given class (or of a descendant class), or
272  nullptr (an empty smart pointer) if not found.
273  * Example:
274  * \code
275  CSphere::Ptr obs = view.getByClass<CSphere>();
276  * \endcode
277  * By default (ith=0), the first observation is returned.
278  */
279  template <typename T>
280  typename T::Ptr getByClass(size_t ith = 0) const
281  {
282  MRPT_START
283  size_t foundCount = 0;
284  const auto* class_ID = &T::GetRuntimeClassIdStatic();
285  for (const auto& o : m_objects)
286  if (o && o->GetRuntimeClass()->derivedFrom(class_ID))
287  if (foundCount++ == ith) return std::dynamic_pointer_cast<T>(o);
288 
289  // If not found directly, search recursively:
290  for (const auto& o : m_objects)
291  {
292  if (o && o->GetRuntimeClass() ==
294  {
295  typename T::Ptr obj = std::dynamic_pointer_cast<T>(
296  std::dynamic_pointer_cast<CSetOfObjects>(o)
297  ->template getByClass<T>(ith));
298  if (obj) return obj;
299  }
300  }
301  return typename T::Ptr(); // Not found: return empty smart pointer
302  MRPT_END
303  }
304 
305  /** Removes the given object from the scene (it also deletes the object to
306  * free its memory).
307  */
308  void removeObject(const CRenderizable::Ptr& obj);
309 
310  /** Number of objects contained. */
311  inline size_t size() const { return m_objects.size(); }
312  inline bool empty() const { return m_objects.empty(); }
313  /** Get a reference to the camera associated with this viewport. */
315  /** Get a reference to the camera associated with this viewport. */
316  const opengl::CCamera& getCamera() const { return m_camera; }
317  /** Evaluates the bounding box of this object (including possible children)
318  * in the coordinate frame of the object parent. */
319  void getBoundingBox(
321 
322  /** @} */ // end of Contained objects set/get/search
323  // ------------------------------------------------------
324 
325  /** Destructor: clears all objects. */
326  ~COpenGLViewport() override;
327 
328  /** Constructor, invoked from COpenGLScene only.
329  */
331  COpenGLScene* parent = nullptr,
332  const std::string& name = std::string(""));
333 
334  /** Render the objects in this viewport (called from COpenGLScene) */
335  void render(
336  const int render_width, const int render_height,
337  const int render_offset_x = 0, const int render_offset_y = 0) const;
338 
339  protected:
340  /** Initializes all textures in the scene (See
341  * opengl::CTexturedPlane::initializeTextures)
342  */
343  void initializeTextures();
344 
345  /** Retrieves a list of all objects in text form.
346  */
347  void dumpListOfObjects(std::vector<std::string>& lst);
348 
349  /** Render in image mode */
350  void renderImageMode() const;
351 
352  /** Render a normal scene with 3D objects */
353  void renderNormalSceneMode() const;
354 
355  /** Render the viewport border, if enabled */
356  void renderViewportBorder() const;
357 
358  /** The camera associated to the viewport */
360  /** The scene that contains this viewport. */
362  /** Set by setCloneView */
363  bool m_isCloned{false};
364  /** Set by setCloneCamera */
365  bool m_isClonedCamera{false};
366  /** Only if m_isCloned=true */
367  std::string m_clonedViewport;
368  /** The viewport's name */
369  std::string m_name;
370  /** Whether to clear color buffer. */
371  bool m_isTransparent{false};
372  /** Default=0, the border around the viewport. */
373  uint32_t m_borderWidth{0};
374 
375  mrpt::img::TColor m_borderColor{255, 255, 255, 255};
376 
377  /** The viewport position [0,1] */
379  /** The min/max clip depth distances (default: 0.1 - 10000) */
380  float m_clip_min = 0.1f, m_clip_max = 10000.0f;
382  /** used only if m_custom_backgb_color */
384  /** Set by setImageView */
385  bool m_isImageView{false};
386 
387  /** The image to display, after calling \a setImageView() */
389 
391 
392  /** Info updated with each "render()" and used in "get3DRayForPixelCoord" */
394 
395  /** Default shader program */
396  mutable std::map<shader_id_t, mrpt::opengl::Program::Ptr> m_shaders;
397 
398  /** Load all MPRT predefined shader programs into m_shaders */
399  void loadDefaultShaders() const;
400 
401  /** Unload shader programs in m_shaders */
402  void unloadShaders();
403 
404  /** The list of objects that comprise the 3D scene.
405  * Objects are automatically deleted when calling "clear" or in the
406  * destructor.
407  */
409 
411 
412  // OpenGL global settings:
414 
416 
417  /** Renders all messages in the underlying class CTextMessageCapable */
418  void renderTextMessages() const;
419 };
420 
421 /**
422  * Inserts an openGL object into a viewport. Allows call chaining.
423  * \sa mrpt::opengl::COpenGLViewport::insert
424  */
427 {
428  s->insert(r);
429  return s;
430 }
431 /**
432  * Inserts any iterable set of openGL objects into a viewport. Allows call
433  * chaining.
434  * \sa mrpt::opengl::COpenGLViewport::insert
435  */
437  COpenGLViewport::Ptr& s, const std::vector<CRenderizable::Ptr>& v)
438 {
439  for (const auto& it : v) s->insert(it);
440  return s;
441 }
442 
443 /** @name Events emitted by COpenGLViewport
444  @{ */
445 
446 /** An event sent by an mrpt::opengl::COpenGLViewport just after clearing the
447  * viewport and setting the GL_PROJECTION matrix, and before calling the scene
448  * OpenGL drawing primitives.
449  *
450  * While handling this event you can call OpenGL glDraw(), etc.
451  *
452  * IMPORTANTE NOTICE: Event handlers in your observer class will most likely be
453  * invoked from an internal GUI thread of MRPT, so all your code in the handler
454  * must be thread safe.
455  */
457 {
458  protected:
459  /** Just to allow this class to be polymorphic */
460  void do_nothing() override {}
461 
462  public:
464  : source_viewport(obj)
465  {
466  }
468 }; // End of class def.
469 
470 /** An event sent by an mrpt::opengl::COpenGLViewport after calling the scene
471  * OpenGL drawing primitives and before doing a glSwapBuffers
472  *
473  * While handling this event you can call OpenGL glBegin(),glEnd(),gl*
474  * functions or those in mrpt::opengl::gl_utils to draw stuff *on the top* of
475  * the normal
476  * objects contained in the COpenGLScene.
477  *
478  * IMPORTANTE NOTICE: Event handlers in your observer class will most likely
479  * be invoked from an internal GUI thread of MRPT,
480  * so all your code in the handler must be thread safe.
481  */
483 {
484  protected:
485  /** Just to allow this class to be polymorphic */
486  void do_nothing() override {}
487 
488  public:
490  : source_viewport(obj)
491  {
492  }
494 }; // End of class def.
495 
496 /** @} */
497 
498 } // namespace mrpt::opengl
float m_clip_min
The min/max clip depth distances (default: 0.1 - 10000)
opengl::CListOpenGLObjects m_objects
The list of objects that comprise the 3D scene.
void setBorderSize(unsigned int lineWidth)
Set the border size ("frame") of the viewport (default=0)
CRenderizable::Ptr getByName(const std::string &str)
Returns the first object with a given name, or nullptr if not found.
void get3DRayForPixelCoord(const double x_coord, const double y_coord, mrpt::math::TLine3D &out_ray, mrpt::poses::CPose3D *out_cameraPose=nullptr) const
Compute the 3D ray corresponding to a given pixel; this can be used to allow the user to pick and sel...
const COpenGLViewport *const source_viewport
const_iterator begin() const
#define MRPT_START
Definition: exceptions.h:241
bool m_isTransparent
Whether to clear color buffer.
opengl::CCamera & getCamera()
Get a reference to the camera associated with this viewport.
mrpt::opengl::CTexturedPlane::Ptr m_imageview_plane
The image to display, after calling setImageView()
A set of object, which are referenced to the coordinates framework established in this object...
Definition: CSetOfObjects.h:26
uint32_t m_borderWidth
Default=0, the border around the viewport.
void setCloneView(const std::string &clonedViewport)
Set this viewport as a clone of some other viewport, given its name - as a side effect, current list of internal OpenGL objects is cleared.
mrptEventGLPostRender(const COpenGLViewport *obj)
The basic event type for the observer-observable pattern in MRPT.
Definition: mrptEvent.h:31
void setViewportClipDistances(const float clip_min, const float clip_max)
Set the min/max clip depth distances of the rendering frustum (default: 0.1 - 10000) ...
std::deque< CRenderizable::Ptr > CListOpenGLObjects
A list of smart pointers to renderizable objects.
Keeps a list of text messages which can be rendered to OpenGL contexts by graphic classes...
void do_nothing() override
Just to allow this class to be polymorphic.
COpenGLScene::Ptr & operator<<(COpenGLScene::Ptr &s, const CRenderizable::Ptr &r)
Inserts an openGL object into a scene.
Definition: COpenGLScene.h:252
TLightParameters & lightParameters()
void setImageView(const mrpt::img::CImage &img)
Set this viewport into "image view"-mode, where an image is efficiently drawn (fitting the viewport a...
void renderImageMode() const
Render in image mode.
void do_nothing() override
Just to allow this class to be polymorphic.
STL namespace.
const opengl::CCamera & getCamera() const
Get a reference to the camera associated with this viewport.
CListOpenGLObjects::iterator iterator
A viewport within a COpenGLScene, containing a set of OpenGL objects to render.
void getViewportClipDistances(float &clip_min, float &clip_max) const
Get the current min/max clip depth distances of the rendering frustum (default: 0.1 - 10000)
void setCloneCamera(bool enable)
If set to true, and setCloneView() has been called, this viewport will be rendered using the camera o...
mrpt::safe_ptr< COpenGLScene > m_parent
The scene that contains this viewport.
TRenderMatrices m_state
Info updated with each "render()" and used in "get3DRayForPixelCoord".
void loadDefaultShaders() const
Load all MPRT predefined shader programs into m_shaders.
const COpenGLViewport *const source_viewport
bool m_isClonedCamera
Set by setCloneCamera.
unsigned int getBorderSize() const
const mrpt::img::TColor & getBorderColor() const
mrpt::img::CImage CImage
Definition: utils/CImage.h:5
bool m_isImageView
Set by setImageView.
void getBoundingBox(mrpt::math::TPoint3D &bb_min, mrpt::math::TPoint3D &bb_max) const
Evaluates the bounding box of this object (including possible children) in the coordinate frame of th...
void render(const int render_width, const int render_height, const int render_offset_x=0, const int render_offset_y=0) const
Render the objects in this viewport (called from COpenGLScene)
~COpenGLViewport() override
Destructor: clears all objects.
void resetCloneView()
Reset the viewport to normal mode: rendering its own objects.
void unloadShaders()
Unload shader programs in m_shaders.
void removeObject(const CRenderizable::Ptr &obj)
Removes the given object from the scene (it also deletes the object to free its memory).
const_iterator end() const
void setNormalMode()
Resets the viewport to a normal 3D viewport.
Inherit from this class for those objects capable of being observed by a CObserver class...
Definition: CObservable.h:31
mrpt::img::TColorf getCustomBackgroundColor() const
Rendering state related to the projection and model-view matrices.
A wrapper class for pointers that can be safely copied with "=" operator without problems.
Definition: safe_pointers.h:71
void getCurrentCameraPose(mrpt::poses::CPose3D &out_cameraPose) const
Compute the current 3D camera pose.
void setBorderColor(const mrpt::img::TColor &c)
void renderTextMessages() const
Renders all messages in the underlying class CTextMessageCapable.
size_t size() const
Number of objects contained.
CListOpenGLObjects::const_iterator const_iterator
mrptEventGLPreRender(const COpenGLViewport *obj)
#define CLASS_ID_NAMESPACE(class_name, namespaceName)
Definition: CObject.h:105
std::map< shader_id_t, mrpt::opengl::Program::Ptr > m_shaders
Default shader program.
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
void clear()
Delete all internal obejcts.
An event sent by an mrpt::opengl::COpenGLViewport just after clearing the viewport and setting the GL...
A class used to store a 3D pose (a 3D translation + a rotation in 3D).
Definition: CPose3D.h:85
void initializeTextures()
Initializes all textures in the scene (See opengl::CTexturedPlane::initializeTextures) ...
An event sent by an mrpt::opengl::COpenGLViewport after calling the scene OpenGL drawing primitives a...
void enablePolygonNicest(bool enable=true)
Sets glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST) is enabled, or GL_FASTEST otherwise.
#define MRPT_END
Definition: exceptions.h:245
An RGBA color - floats in the range [0,1].
Definition: TColor.h:88
mrpt::opengl::CSetOfLines::Ptr m_borderLines
The namespace for 3D scene representation and rendering.
Definition: CGlCanvasBase.h:13
This class allows the user to create, load, save, and render 3D scenes using OpenGL primitives...
Definition: COpenGLScene.h:56
void setTransparent(bool trans)
Set the transparency, that is, whether the viewport will be rendered transparent over previous viewpo...
const auto bb_max
The virtual base class which provides a unified interface for all persistent objects in MRPT...
Definition: CSerializable.h:30
std::string m_clonedViewport
Only if m_isCloned=true.
std::string getName()
Returns the name of the viewport.
mrpt::img::TColorf m_background_color
used only if m_custom_backgb_color
void renderNormalSceneMode() const
Render a normal scene with 3D objects.
#define DEFINE_SERIALIZABLE(class_name, NS)
This declaration must be inserted in all CSerializable classes definition, within the class declarati...
const auto bb_min
void setCustomBackgroundColor(const mrpt::img::TColorf &color)
Set a background color different from that of the parent GUI window.
A RGB color - 8bit.
Definition: TColor.h:25
A camera: if added to a scene, the viewpoint defined by this camera will be used instead of the camer...
Definition: CCamera.h:33
void renderViewportBorder() const
Render the viewport border, if enabled.
const TLightParameters & lightParameters() const
bool isTransparent()
Return whether the viewport will be rendered transparent over previous viewports. ...
void setViewportPosition(const double x, const double y, const double width, const double height)
Change the viewport position and dimension on the rendering window.
void dumpListOfObjects(std::vector< std::string > &lst)
Retrieves a list of all objects in text form.
void getViewportPosition(double &x, double &y, double &width, double &height)
Get the current viewport position and dimension on the rendering window.
void insert(const CRenderizable::Ptr &newObject)
Insert a new object into the list.
T::Ptr getByClass(size_t ith=0) const
Returns the i&#39;th object of a given class (or of a descendant class), or nullptr (an empty smart point...
double m_view_x
The viewport position [0,1].
Lighting parameters, mostly for triangle shaders.
void setCurrentCameraFromPose(mrpt::poses::CPose3D &p)
Changes the point of view of the camera, from a given pose.
opengl::CCamera m_camera
The camera associated to the viewport.
std::string m_name
The viewport&#39;s name.
COpenGLViewport(COpenGLScene *parent=nullptr, const std::string &name=std::string(""))
Constructor, invoked from COpenGLScene only.
bool m_isCloned
Set by setCloneView.
3D line, represented by a base point and a director vector.
Definition: TLine3D.h:19



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: c7a3bec24 Sun Mar 29 18:33:13 2020 +0200 at dom mar 29 18:50:38 CEST 2020