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