]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/elements/path.cxx
Canvas: Forward mouse events to elements.
[flightgear.git] / src / Canvas / elements / path.cxx
1 // An OpenVG path on the canvas
2 //
3 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19 #include "path.hxx"
20 #include <Canvas/property_helper.hxx>
21
22 #include <vg/openvg.h>
23
24 #include <osg/Drawable>
25 #include <osg/BlendFunc>
26
27 #include <cassert>
28
29 namespace canvas
30 {
31   typedef std::vector<VGubyte>  CmdList;
32   typedef std::vector<VGfloat>  CoordList;
33
34   class PathDrawable:
35     public osg::Drawable
36   {
37     public:
38       PathDrawable():
39         _path(VG_INVALID_HANDLE),
40         _paint(VG_INVALID_HANDLE),
41         _paint_fill(VG_INVALID_HANDLE),
42         _attributes_dirty(~0),
43         _stroke_width(1),
44         _stroke_linecap(VG_CAP_BUTT),
45         _fill(false)
46       {
47         setSupportsDisplayList(false);
48         setDataVariance(Object::DYNAMIC);
49
50         setUpdateCallback(new PathUpdateCallback());
51         setCullCallback(new NoCullCallback());
52       }
53
54       virtual ~PathDrawable()
55       {
56         vgDestroyPath(_path);
57         vgDestroyPaint(_paint);
58       }
59
60       virtual const char* className() const { return "PathDrawable"; }
61       virtual osg::Object* cloneType() const { return new PathDrawable; }
62       virtual osg::Object* clone(const osg::CopyOp&) const { return new PathDrawable; }
63
64       /**
65        * Replace the current path segments with the new ones
66        *
67        * @param cmds    List of OpenVG path commands
68        * @param coords  List of coordinates/parameters used by #cmds
69        */
70       void setSegments(const CmdList& cmds, const CoordList& coords)
71       {
72         _cmds = cmds;
73         _coords = coords;
74
75         _attributes_dirty |= PATH;
76       }
77
78       /**
79        * Set stroke width and dash (line stipple)
80        */
81       void setStroke( float width,
82                       const std::vector<float> dash = std::vector<float>() )
83       {
84         _stroke_width = width;
85         _stroke_dash = dash;
86
87         _attributes_dirty |= BOUNDING_BOX;
88       }
89
90       /**
91        * Set the line color
92        */
93       void setColor(const osg::Vec4& color)
94       {
95         for( size_t i = 0; i < 4; ++i )
96           _stroke_color[i] = color[i];
97         _attributes_dirty |= STROKE_COLOR;
98       }
99
100       /**
101        * Enable/Disable filling of the path
102        */
103       void enableFill(bool enable)
104       {
105         _fill = enable;
106       }
107
108       /**
109        * Set the line color
110        */
111       void setColorFill(const osg::Vec4& color)
112       {
113         for( size_t i = 0; i < 4; ++i )
114           _fill_color[i] = color[i];
115         _attributes_dirty |= FILL_COLOR;
116       }
117
118       /**
119        * Set stroke-linecap
120        *
121        * @see http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty
122        */
123       void setStrokeLinecap(const std::string& linecap)
124       {
125         if( linecap == "round" )
126           _stroke_linecap = VG_CAP_ROUND;
127         else if( linecap == "square" )
128           _stroke_linecap = VG_CAP_SQUARE;
129         else
130           _stroke_linecap = VG_CAP_BUTT;
131       }
132
133       /**
134        * Draw callback
135        */
136       virtual void drawImplementation(osg::RenderInfo& renderInfo) const
137       {
138         if( (_attributes_dirty & PATH) && _vg_initialized )
139           return;
140
141         osg::State* state = renderInfo.getState();
142         assert(state);
143
144         state->setActiveTextureUnit(0);
145         state->setClientActiveTextureUnit(0);
146         state->disableAllVertexArrays();
147
148         glPushAttrib(~0u); // Don't use GL_ALL_ATTRIB_BITS as on my machine it
149                            // eg. doesn't include GL_MULTISAMPLE_BIT
150         glPushClientAttrib(~0u);
151
152         // Initialize OpenVG itself
153         if( !_vg_initialized )
154         {
155           GLint vp[4];
156           glGetIntegerv(GL_VIEWPORT, vp);
157
158           vgCreateContextSH(vp[2], vp[3]);
159           _vg_initialized = true;
160           return;
161         }
162
163         // Initialize/Update the paint
164         if( _attributes_dirty & STROKE_COLOR )
165         {
166           if( _paint == VG_INVALID_HANDLE )
167             _paint = vgCreatePaint();
168
169           vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, _stroke_color);
170
171           _attributes_dirty &= ~STROKE_COLOR;
172         }
173
174         // Initialize/update fill paint
175         if( _attributes_dirty & (FILL_COLOR | FILL) )
176         {
177           if( _paint_fill == VG_INVALID_HANDLE )
178             _paint_fill = vgCreatePaint();
179
180           if( _attributes_dirty & FILL_COLOR )
181             vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, _fill_color);
182
183           _attributes_dirty &= ~(FILL_COLOR | FILL);
184         }
185
186         // Detect draw mode
187         VGbitfield mode = 0;
188         if( _stroke_width > 0 )
189         {
190           mode |= VG_STROKE_PATH;
191           vgSetPaint(_paint, VG_STROKE_PATH);
192
193           vgSetf(VG_STROKE_LINE_WIDTH, _stroke_width);
194           vgSeti(VG_STROKE_CAP_STYLE, _stroke_linecap);
195           vgSetfv( VG_STROKE_DASH_PATTERN,
196                    _stroke_dash.size(),
197                    _stroke_dash.empty() ? 0 : &_stroke_dash[0] );
198         }
199         if( _fill )
200         {
201           mode |= VG_FILL_PATH;
202           vgSetPaint(_paint_fill, VG_FILL_PATH);
203         }
204
205         // And finally draw the path
206         if( mode )
207           vgDrawPath(_path, mode);
208
209         VGErrorCode err = vgGetError();
210         if( err != VG_NO_ERROR )
211           SG_LOG(SG_GL, SG_ALERT, "vgError: " << err);
212
213         glPopAttrib();
214         glPopClientAttrib();
215       }
216
217       /**
218        * Compute the bounding box
219        */
220       virtual osg::BoundingBox computeBound() const
221       {
222         if( _path == VG_INVALID_HANDLE )
223           return osg::BoundingBox();
224
225         VGfloat min[2], size[2];
226         vgPathBounds(_path, &min[0], &min[1], &size[0], &size[1]);
227
228         _attributes_dirty &= ~BOUNDING_BOX;
229
230         // vgPathBounds doesn't take stroke width into account
231         float ext = 0.5 * _stroke_width;
232
233         return osg::BoundingBox
234         (
235           min[0] - ext,           min[1] - ext,           -0.1,
236           min[0] + size[0] + ext, min[1] + size[1] + ext,  0.1
237         );
238       }
239
240     private:
241
242       static bool _vg_initialized;
243
244       enum Attributes
245       {
246         PATH            = 0x0001,
247         STROKE_COLOR    = PATH << 1,
248         FILL_COLOR      = STROKE_COLOR << 1,
249         FILL            = FILL_COLOR << 1,
250         BOUNDING_BOX    = FILL << 1
251       };
252
253       mutable VGPath    _path;
254       mutable VGPaint   _paint;
255       mutable VGPaint   _paint_fill;
256       mutable uint32_t  _attributes_dirty;
257
258       CmdList   _cmds;
259       CoordList _coords;
260
261       VGfloat               _stroke_color[4];
262       VGfloat               _stroke_width;
263       std::vector<VGfloat>  _stroke_dash;
264       VGCapStyle            _stroke_linecap;
265
266       bool      _fill;
267       VGfloat   _fill_color[4];
268
269       /**
270        * Initialize/Update the OpenVG path
271        */
272       void update()
273       {
274         if( _attributes_dirty & PATH )
275         {
276           const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
277                                 | VG_PATH_CAPABILITY_MODIFY
278                                 | VG_PATH_CAPABILITY_PATH_BOUNDS;
279
280           if( _path == VG_INVALID_HANDLE )
281             _path = vgCreatePath(
282               VG_PATH_FORMAT_STANDARD,
283               VG_PATH_DATATYPE_F,
284               1.f, 0.f, // scale,bias
285               _cmds.size(), _coords.size(),
286               caps
287             );
288           else
289             vgClearPath(_path, caps);
290
291           if( !_cmds.empty() && !_coords.empty() )
292             vgAppendPathData(_path, _cmds.size(), &_cmds[0], &_coords[0]);
293
294           _attributes_dirty &= ~PATH;
295           _attributes_dirty |= BOUNDING_BOX;
296         }
297
298         if( _attributes_dirty & BOUNDING_BOX )
299           dirtyBound();
300       }
301
302       /**
303        * Updating the path before drawing is needed to enable correct bounding
304        * box calculations and make culling work.
305        */
306       struct PathUpdateCallback:
307         public osg::Drawable::UpdateCallback
308       {
309         virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
310         {
311           if( !_vg_initialized )
312             return;
313           static_cast<PathDrawable*>(drawable)->update();
314         }
315       };
316
317       /**
318        * Callback used to prevent culling as long as OpenVG is not initialized.
319        * This is needed because OpenVG needs an active OpenGL context for
320        * initialization which is only available in #drawImplementation.
321        * As soon as OpenVG is correctly initialized the callback automatically
322        * removes itself from the node, so that the normal culling can get
323        * active.
324        */
325       struct NoCullCallback:
326         public osg::Drawable::CullCallback
327       {
328         virtual bool cull( osg::NodeVisitor*,
329                            osg::Drawable* drawable,
330                            osg::State* ) const
331         {
332           if( _vg_initialized )
333             drawable->setCullCallback(0);
334           return false;
335         }
336       };
337   };
338
339   bool PathDrawable::_vg_initialized = false;
340
341   //----------------------------------------------------------------------------
342   Path::Path(SGPropertyNode_ptr node):
343     Element(node, COLOR | COLOR_FILL | BOUNDING_BOX),
344     _path( new PathDrawable() )
345   {
346     setDrawable(_path);
347   }
348
349   //----------------------------------------------------------------------------
350   Path::~Path()
351   {
352
353   }
354
355   //----------------------------------------------------------------------------
356   void Path::update(double dt)
357   {
358     if( _attributes_dirty & (CMDS | COORDS) )
359     {
360       _path->setSegments
361       (
362         getVectorFromChildren<VGubyte, int>(_node, "cmd"),
363         getVectorFromChildren<VGfloat, float>(_node, "coord")
364       );
365
366       _attributes_dirty &= ~(CMDS | COORDS);
367     }
368     if( _attributes_dirty & STROKE )
369     {
370       _path->setStroke
371       (
372         _node->getFloatValue("stroke-width", 1),
373         getVectorFromChildren<VGfloat, float>(_node, "stroke-dasharray")
374       );
375
376       _attributes_dirty &= ~STROKE;
377     }
378
379     Element::update(dt);
380   }
381
382   //----------------------------------------------------------------------------
383   void Path::childChanged(SGPropertyNode* child)
384   {
385     if( child->getNameString() == "cmd" )
386       _attributes_dirty |= CMDS;
387     else if( child->getNameString() == "coord" )
388       _attributes_dirty |= COORDS;
389     else if(    child->getNameString() == "stroke-width"
390              || child->getNameString() == "stroke-dasharray" )
391       _attributes_dirty |= STROKE;
392     else if( child->getNameString() == "stroke-linecap" )
393       _path->setStrokeLinecap( child->getStringValue() );
394     else if( child->getNameString() == "fill" )
395       _path->enableFill( child->getBoolValue() );
396   }
397
398   //----------------------------------------------------------------------------
399   void Path::colorChanged(const osg::Vec4& color)
400   {
401     _path->setColor(color);
402   }
403
404   //----------------------------------------------------------------------------
405   void Path::colorFillChanged(const osg::Vec4& color)
406   {
407     _path->setColorFill(color);
408   }
409
410 } // namespace canvas