]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/elements/path.cxx
Fix a Clang warning in Shiva.
[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         _mode(0),
44         _stroke_width(1),
45         _stroke_linecap(VG_CAP_BUTT)
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 path fill paint ("none" if not filled)
80        */
81       void setFill(const std::string& fill)
82       {
83         if( fill == "none" )
84         {
85           _mode &= ~VG_FILL_PATH;
86         }
87         else
88         {
89           _fill_color = parseColor(fill);
90           _mode |= VG_FILL_PATH;
91           _attributes_dirty |= FILL_COLOR;
92         }
93       }
94
95       /**
96        * Set path stroke paint ("none" if no stroke)
97        */
98       void setStroke(const std::string& stroke)
99       {
100         if( stroke == "none" )
101         {
102           _mode &= ~VG_STROKE_PATH;
103         }
104         else
105         {
106           _stroke_color = parseColor(stroke);
107           _mode |= VG_STROKE_PATH;
108           _attributes_dirty |= STROKE_COLOR;
109         }
110       }
111
112       /**
113        * Set stroke width
114        */
115       void setStrokeWidth(float width)
116       {
117         _stroke_width = width;
118         _attributes_dirty |= BOUNDING_BOX;
119       }
120
121       /**
122        * Set stroke dash (line stipple)
123        */
124       void setStrokeDashArray(const std::string& dash)
125       {
126         _stroke_dash = splitAndConvert(",\t\n ", dash);
127       }
128
129       /**
130        * Set stroke-linecap
131        *
132        * @see http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty
133        */
134       void setStrokeLinecap(const std::string& linecap)
135       {
136         if( linecap == "round" )
137           _stroke_linecap = VG_CAP_ROUND;
138         else if( linecap == "square" )
139           _stroke_linecap = VG_CAP_SQUARE;
140         else
141           _stroke_linecap = VG_CAP_BUTT;
142       }
143
144       /**
145        * Draw callback
146        */
147       virtual void drawImplementation(osg::RenderInfo& renderInfo) const
148       {
149         if( (_attributes_dirty & PATH) && _vg_initialized )
150           return;
151
152         osg::State* state = renderInfo.getState();
153         assert(state);
154
155         state->setActiveTextureUnit(0);
156         state->setClientActiveTextureUnit(0);
157         state->disableAllVertexArrays();
158
159         glPushAttrib(~0u); // Don't use GL_ALL_ATTRIB_BITS as on my machine it
160                            // eg. doesn't include GL_MULTISAMPLE_BIT
161         glPushClientAttrib(~0u);
162
163         // Initialize OpenVG itself
164         if( !_vg_initialized )
165         {
166           GLint vp[4];
167           glGetIntegerv(GL_VIEWPORT, vp);
168
169           vgCreateContextSH(vp[2], vp[3]);
170           _vg_initialized = true;
171           return;
172         }
173
174         // Initialize/Update the paint
175         if( _attributes_dirty & STROKE_COLOR )
176         {
177           if( _paint == VG_INVALID_HANDLE )
178             _paint = vgCreatePaint();
179
180           vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, _stroke_color._v);
181
182           _attributes_dirty &= ~STROKE_COLOR;
183         }
184
185         // Initialize/update fill paint
186         if( _attributes_dirty & FILL_COLOR )
187         {
188           if( _paint_fill == VG_INVALID_HANDLE )
189             _paint_fill = vgCreatePaint();
190
191           vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, _fill_color._v);
192
193           _attributes_dirty &= ~FILL_COLOR;
194         }
195
196         // Setup paint
197         if( _mode & VG_STROKE_PATH )
198         {
199           vgSetPaint(_paint, VG_STROKE_PATH);
200
201           vgSetf(VG_STROKE_LINE_WIDTH, _stroke_width);
202           vgSeti(VG_STROKE_CAP_STYLE, _stroke_linecap);
203           vgSetfv( VG_STROKE_DASH_PATTERN,
204                    _stroke_dash.size(),
205                    _stroke_dash.empty() ? 0 : &_stroke_dash[0] );
206         }
207         if( _mode & VG_FILL_PATH )
208         {
209           vgSetPaint(_paint_fill, VG_FILL_PATH);
210         }
211
212         // And finally draw the path
213         if( _mode )
214           vgDrawPath(_path, _mode);
215
216         VGErrorCode err = vgGetError();
217         if( err != VG_NO_ERROR )
218           SG_LOG(SG_GL, SG_ALERT, "vgError: " << err);
219
220         glPopAttrib();
221         glPopClientAttrib();
222       }
223
224       /**
225        * Compute the bounding box
226        */
227       virtual osg::BoundingBox computeBound() const
228       {
229         if( _path == VG_INVALID_HANDLE )
230           return osg::BoundingBox();
231
232         VGfloat min[2], size[2];
233         vgPathBounds(_path, &min[0], &min[1], &size[0], &size[1]);
234
235         _attributes_dirty &= ~BOUNDING_BOX;
236
237         // vgPathBounds doesn't take stroke width into account
238         float ext = 0.5 * _stroke_width;
239
240         return osg::BoundingBox
241         (
242           min[0] - ext,           min[1] - ext,           -0.1,
243           min[0] + size[0] + ext, min[1] + size[1] + ext,  0.1
244         );
245       }
246
247     private:
248
249       static bool _vg_initialized;
250
251       enum Attributes
252       {
253         PATH            = 0x0001,
254         STROKE_COLOR    = PATH << 1,
255         FILL_COLOR      = STROKE_COLOR << 1,
256         BOUNDING_BOX    = FILL_COLOR << 1
257       };
258
259       mutable VGPath    _path;
260       mutable VGPaint   _paint;
261       mutable VGPaint   _paint_fill;
262       mutable uint32_t  _attributes_dirty;
263
264       CmdList   _cmds;
265       CoordList _coords;
266
267       VGbitfield            _mode;
268       osg::Vec4f            _fill_color;
269       osg::Vec4f            _stroke_color;
270       VGfloat               _stroke_width;
271       std::vector<VGfloat>  _stroke_dash;
272       VGCapStyle            _stroke_linecap;
273
274       /**
275        * Initialize/Update the OpenVG path
276        */
277       void update()
278       {
279         if( _attributes_dirty & PATH )
280         {
281           const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
282                                 | VG_PATH_CAPABILITY_MODIFY
283                                 | VG_PATH_CAPABILITY_PATH_BOUNDS;
284
285           if( _path == VG_INVALID_HANDLE )
286             _path = vgCreatePath(
287               VG_PATH_FORMAT_STANDARD,
288               VG_PATH_DATATYPE_F,
289               1.f, 0.f, // scale,bias
290               _cmds.size(), _coords.size(),
291               caps
292             );
293           else
294             vgClearPath(_path, caps);
295
296           if( !_cmds.empty() && !_coords.empty() )
297             vgAppendPathData(_path, _cmds.size(), &_cmds[0], &_coords[0]);
298
299           _attributes_dirty &= ~PATH;
300           _attributes_dirty |= BOUNDING_BOX;
301         }
302
303         if( _attributes_dirty & BOUNDING_BOX )
304           dirtyBound();
305       }
306
307       /**
308        * Updating the path before drawing is needed to enable correct bounding
309        * box calculations and make culling work.
310        */
311       struct PathUpdateCallback:
312         public osg::Drawable::UpdateCallback
313       {
314         virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
315         {
316           if( !_vg_initialized )
317             return;
318           static_cast<PathDrawable*>(drawable)->update();
319         }
320       };
321
322       /**
323        * Callback used to prevent culling as long as OpenVG is not initialized.
324        * This is needed because OpenVG needs an active OpenGL context for
325        * initialization which is only available in #drawImplementation.
326        * As soon as OpenVG is correctly initialized the callback automatically
327        * removes itself from the node, so that the normal culling can get
328        * active.
329        */
330       struct NoCullCallback:
331         public osg::Drawable::CullCallback
332       {
333         virtual bool cull( osg::NodeVisitor*,
334                            osg::Drawable* drawable,
335                            osg::State* ) const
336         {
337           if( _vg_initialized )
338             drawable->setCullCallback(0);
339           return false;
340         }
341       };
342   };
343
344   bool PathDrawable::_vg_initialized = false;
345
346   //----------------------------------------------------------------------------
347   Path::Path(SGPropertyNode_ptr node, const Style& parent_style):
348     Element(node, parent_style, BOUNDING_BOX),
349     _path( new PathDrawable() )
350   {
351     setDrawable(_path);
352     PathDrawable *path = _path.get();
353
354     addStyle("fill", &PathDrawable::setFill, path);
355     addStyle("stroke", &PathDrawable::setStroke, path);
356     addStyle("stroke-width", &PathDrawable::setStrokeWidth, path);
357     addStyle("stroke-dasharray", &PathDrawable::setStrokeDashArray, path);
358     addStyle("stroke-linecap", &PathDrawable::setStrokeLinecap, path);
359
360     setupStyle();
361   }
362
363   //----------------------------------------------------------------------------
364   Path::~Path()
365   {
366
367   }
368
369   //----------------------------------------------------------------------------
370   void Path::update(double dt)
371   {
372     if( _attributes_dirty & (CMDS | COORDS) )
373     {
374       _path->setSegments
375       (
376         getVectorFromChildren<VGubyte, int>(_node, "cmd"),
377         getVectorFromChildren<VGfloat, float>(_node, "coord")
378       );
379
380       _attributes_dirty &= ~(CMDS | COORDS);
381     }
382
383     Element::update(dt);
384   }
385
386   //----------------------------------------------------------------------------
387   void Path::childChanged(SGPropertyNode* child)
388   {
389     if( child->getParent() != _node )
390       return;
391
392     if( child->getNameString() == "cmd" )
393       _attributes_dirty |= CMDS;
394     else if( child->getNameString() == "coord" )
395       _attributes_dirty |= COORDS;
396   }
397
398 } // namespace canvas