]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasPath.cxx
CanvasImage: Use image/canvas size rather than texture size
[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   static const VGubyte shCoordsPerCommand[] = {
37     0, /* VG_CLOSE_PATH */
38     2, /* VG_MOVE_TO */
39     2, /* VG_LINE_TO */
40     1, /* VG_HLINE_TO */
41     1, /* VG_VLINE_TO */
42     4, /* VG_QUAD_TO */
43     6, /* VG_CUBIC_TO */
44     2, /* VG_SQUAD_TO */
45     4, /* VG_SCUBIC_TO */
46     5, /* VG_SCCWARC_TO */
47     5, /* VG_SCWARC_TO */
48     5, /* VG_LCCWARC_TO */
49     5  /* VG_LCWARC_TO */
50   };
51   static const VGubyte shNumCommands = sizeof(shCoordsPerCommand)
52                                      / sizeof(shCoordsPerCommand[0]);
53
54   /**
55    * Helper to split and convert comma/whitespace separated floating point
56    * values
57    */
58   std::vector<float> splitAndConvert(const char del[], const std::string& str);
59
60   class Path::PathDrawable:
61     public osg::Drawable
62   {
63     public:
64       PathDrawable(Path* path):
65         _path_element(path),
66         _path(VG_INVALID_HANDLE),
67         _paint(VG_INVALID_HANDLE),
68         _paint_fill(VG_INVALID_HANDLE),
69         _attributes_dirty(~0),
70         _mode(0),
71         _fill_rule(VG_EVEN_ODD),
72         _stroke_width(1),
73         _stroke_linecap(VG_CAP_BUTT)
74       {
75         setSupportsDisplayList(false);
76         setDataVariance(Object::DYNAMIC);
77
78         setUpdateCallback(new PathUpdateCallback());
79       }
80
81       virtual ~PathDrawable()
82       {
83         if( _path != VG_INVALID_HANDLE )
84           vgDestroyPath(_path);
85         if( _paint != VG_INVALID_HANDLE )
86           vgDestroyPaint(_paint);
87         if( _paint_fill != VG_INVALID_HANDLE )
88           vgDestroyPaint(_paint_fill);
89       }
90
91       virtual const char* className() const { return "PathDrawable"; }
92       virtual osg::Object* cloneType() const { return new PathDrawable(_path_element); }
93       virtual osg::Object* clone(const osg::CopyOp&) const { return new PathDrawable(_path_element); }
94
95       /**
96        * Replace the current path segments with the new ones
97        *
98        * @param cmds    List of OpenVG path commands
99        * @param coords  List of coordinates/parameters used by #cmds
100        */
101       void setSegments(const CmdList& cmds, const CoordList& coords)
102       {
103         _cmds = cmds;
104         _coords = coords;
105
106         _attributes_dirty |= (PATH | BOUNDING_BOX);
107       }
108
109       /**
110        * Set path fill paint ("none" if not filled)
111        */
112       void setFill(const std::string& fill)
113       {
114         if( fill == "none" )
115         {
116           _mode &= ~VG_FILL_PATH;
117         }
118         else if( parseColor(fill, _fill_color) )
119         {
120           _mode |= VG_FILL_PATH;
121           _attributes_dirty |= FILL_COLOR;
122         }
123         else
124         {
125           SG_LOG
126           (
127             SG_GENERAL,
128             SG_WARN,
129             "canvas::Path Unknown fill: " << fill
130           );
131         }
132       }
133
134       /**
135        * Set path fill rule ("pseudo-nonzero" or "evenodd")
136        *
137        * @warning As the current nonzero implementation causes sever artifacts
138        *          for every concave path we call it pseudo-nonzero, so that
139        *          everyone is warned that it won't work as expected :)
140        */
141       void setFillRule(const std::string& fill_rule)
142       {
143         if( fill_rule == "pseudo-nonzero" )
144           _fill_rule = VG_NON_ZERO;
145         else // if( fill_rule == "evenodd" )
146           _fill_rule = VG_EVEN_ODD;
147       }
148
149       /**
150        * Set path stroke paint ("none" if no stroke)
151        */
152       void setStroke(const std::string& stroke)
153       {
154         if( stroke == "none" )
155         {
156           _mode &= ~VG_STROKE_PATH;
157         }
158         else if( parseColor(stroke, _stroke_color) )
159         {
160           _mode |= VG_STROKE_PATH;
161                     _attributes_dirty |= STROKE_COLOR;
162         }
163         else
164         {
165           SG_LOG
166           (
167             SG_GENERAL,
168             SG_WARN,
169             "canvas::Path Unknown stroke: " << stroke
170           );
171         }
172       }
173
174       /**
175        * Set stroke width
176        */
177       void setStrokeWidth(float width)
178       {
179         _stroke_width = width;
180         _attributes_dirty |= BOUNDING_BOX;
181       }
182
183       /**
184        * Set stroke dash (line stipple)
185        */
186       void setStrokeDashArray(const std::string& dash)
187       {
188         _stroke_dash = splitAndConvert(",\t\n ", dash);
189       }
190
191       /**
192        * Set stroke-linecap
193        *
194        * @see http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty
195        */
196       void setStrokeLinecap(const std::string& linecap)
197       {
198         if( linecap == "round" )
199           _stroke_linecap = VG_CAP_ROUND;
200         else if( linecap == "square" )
201           _stroke_linecap = VG_CAP_SQUARE;
202         else
203           _stroke_linecap = VG_CAP_BUTT;
204       }
205
206       /**
207        * Draw callback
208        */
209       virtual void drawImplementation(osg::RenderInfo& renderInfo) const
210       {
211         if( _attributes_dirty & PATH )
212           return;
213
214         osg::State* state = renderInfo.getState();
215         assert(state);
216
217         state->setActiveTextureUnit(0);
218         state->setClientActiveTextureUnit(0);
219         state->disableAllVertexArrays();
220
221         glPushAttrib(~0u); // Don't use GL_ALL_ATTRIB_BITS as on my machine it
222                            // eg. doesn't include GL_MULTISAMPLE_BIT
223         glPushClientAttrib(~0u);
224
225         // Initialize/Update the paint
226         if( _attributes_dirty & STROKE_COLOR )
227         {
228           if( _paint == VG_INVALID_HANDLE )
229             _paint = vgCreatePaint();
230
231           vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, _stroke_color._v);
232
233           _attributes_dirty &= ~STROKE_COLOR;
234         }
235
236         // Initialize/update fill paint
237         if( _attributes_dirty & FILL_COLOR )
238         {
239           if( _paint_fill == VG_INVALID_HANDLE )
240             _paint_fill = vgCreatePaint();
241
242           vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, _fill_color._v);
243
244           _attributes_dirty &= ~FILL_COLOR;
245         }
246
247         // Setup paint
248         if( _mode & VG_STROKE_PATH )
249         {
250           vgSetPaint(_paint, VG_STROKE_PATH);
251
252           vgSetf(VG_STROKE_LINE_WIDTH, _stroke_width);
253           vgSeti(VG_STROKE_CAP_STYLE, _stroke_linecap);
254           vgSetfv( VG_STROKE_DASH_PATTERN,
255                    _stroke_dash.size(),
256                    _stroke_dash.empty() ? 0 : &_stroke_dash[0] );
257         }
258         if( _mode & VG_FILL_PATH )
259         {
260           vgSetPaint(_paint_fill, VG_FILL_PATH);
261
262           vgSeti(VG_FILL_RULE, _fill_rule);
263         }
264
265         // And finally draw the path
266         if( _mode )
267           vgDrawPath(_path, _mode);
268
269         VGErrorCode err = vgGetError();
270         if( err != VG_NO_ERROR )
271           SG_LOG(SG_GL, SG_ALERT, "vgError: " << err);
272
273         glPopAttrib();
274         glPopClientAttrib();
275       }
276
277       osg::BoundingBox getTransformedBounds(const osg::Matrix& mat) const
278       {
279         osg::BoundingBox bb;
280
281         osg::Vec2f cur; // VG "Current point" (in local coordinates)
282         VGubyte cmd_index = 0;
283         for( size_t i = 0,              ci = 0;
284                     i < _cmds.size() && ci < _coords.size();
285                   ++i,                  ci += shCoordsPerCommand[cmd_index] )
286         {
287           VGubyte rel = _cmds[i] & 1,
288                   cmd = _cmds[i] & ~1;
289
290           cmd_index = cmd / 2;
291           if( cmd_index >= shNumCommands )
292             return osg::BoundingBox();
293
294           const VGubyte max_coords = 3;
295           osg::Vec2f points[max_coords];
296           VGubyte num_coords = 0;
297
298           switch( cmd )
299           {
300             case VG_CLOSE_PATH:
301               break;
302             case VG_MOVE_TO:
303             case VG_LINE_TO:
304             case VG_SQUAD_TO:
305               // x0, y0
306               points[ num_coords++ ].set(_coords[ci], _coords[ci + 1]);
307               break;
308             case VG_HLINE_TO:
309               // x0
310               points[ num_coords++ ].set( _coords[ci] + (rel ? cur.x() : 0),
311                                           cur.y() );
312               // We have handled rel/abs already, so no need to do it again...
313               rel = 0;
314               break;
315             case VG_VLINE_TO:
316               // y0
317               points[ num_coords++ ].set( cur.x(),
318                                           _coords[ci] + (rel ? cur.y() : 0) );
319               // We have handled rel/abs already, so no need to do it again...
320               rel = 0;
321               break;
322             case VG_QUAD_TO:
323             case VG_SCUBIC_TO:
324               // x0,y0,x1,y1
325               points[ num_coords++ ].set(_coords[ci    ], _coords[ci + 1]);
326               points[ num_coords++ ].set(_coords[ci + 2], _coords[ci + 3]);
327               break;
328             case VG_CUBIC_TO:
329               // x0,y0,x1,y1,x2,y2
330               points[ num_coords++ ].set(_coords[ci    ], _coords[ci + 1]);
331               points[ num_coords++ ].set(_coords[ci + 2], _coords[ci + 3]);
332               points[ num_coords++ ].set(_coords[ci + 4], _coords[ci + 5]);
333               break;
334             case VG_SCCWARC_TO:
335             case VG_SCWARC_TO:
336             case VG_LCCWARC_TO:
337             case VG_LCWARC_TO:
338               // rh,rv,rot,x0,y0
339               points[ num_coords++ ].set(_coords[ci + 3], _coords[ci + 4]);
340               break;
341             default:
342               SG_LOG(SG_GL, SG_WARN, "Unknown VG command: " << (int)cmd);
343               return osg::BoundingBox();
344           }
345
346           assert(num_coords <= max_coords);
347           for(VGubyte i = 0; i < num_coords; ++i)
348           {
349             if( rel )
350               points[i] += cur;
351
352             bb.expandBy( transformPoint(mat, points[i]) );
353           }
354
355           if( num_coords > 0 )
356             cur = points[ num_coords - 1 ];
357         }
358
359         return bb;
360       }
361
362       /**
363        * Compute the bounding box
364        */
365       virtual osg::BoundingBox computeBound() const
366       {
367         if( _path == VG_INVALID_HANDLE || (_attributes_dirty & PATH) )
368           return osg::BoundingBox();
369
370         VGfloat min[2], size[2];
371         vgPathBounds(_path, &min[0], &min[1], &size[0], &size[1]);
372
373         _attributes_dirty &= ~BOUNDING_BOX;
374
375         // vgPathBounds doesn't take stroke width into account
376         float ext = 0.5 * _stroke_width;
377
378         osg::BoundingBox bb
379         (
380           min[0] - ext,           min[1] - ext,           -0.1,
381           min[0] + size[0] + ext, min[1] + size[1] + ext,  0.1
382         );
383         _path_element->setBoundingBox(bb);
384
385         return bb;
386       }
387
388     private:
389
390       enum Attributes
391       {
392         PATH            = 0x0001,
393         STROKE_COLOR    = PATH << 1,
394         FILL_COLOR      = STROKE_COLOR << 1,
395         BOUNDING_BOX    = FILL_COLOR << 1
396       };
397
398       Path *_path_element;
399
400       mutable VGPath    _path;
401       mutable VGPaint   _paint;
402       mutable VGPaint   _paint_fill;
403       mutable uint32_t  _attributes_dirty;
404
405       CmdList   _cmds;
406       CoordList _coords;
407
408       VGbitfield            _mode;
409       osg::Vec4f            _fill_color;
410       VGFillRule            _fill_rule;
411       osg::Vec4f            _stroke_color;
412       VGfloat               _stroke_width;
413       std::vector<VGfloat>  _stroke_dash;
414       VGCapStyle            _stroke_linecap;
415
416       osg::Vec3f transformPoint( const osg::Matrix& m,
417                                  osg::Vec2f pos ) const
418       {
419         return osg::Vec3
420         (
421           m(0, 0) * pos[0] + m(1, 0) * pos[1] + m(3, 0),
422           m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1),
423           0
424         );
425       }
426
427       /**
428        * Initialize/Update the OpenVG path
429        */
430       void update()
431       {
432         if( _attributes_dirty & PATH )
433         {
434           const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
435                                 | VG_PATH_CAPABILITY_MODIFY
436                                 | VG_PATH_CAPABILITY_PATH_BOUNDS;
437
438           if( _path == VG_INVALID_HANDLE )
439             _path = vgCreatePath(
440               VG_PATH_FORMAT_STANDARD,
441               VG_PATH_DATATYPE_F,
442               1.f, 0.f, // scale,bias
443               _cmds.size(), _coords.size(),
444               caps
445             );
446           else
447             vgClearPath(_path, caps);
448
449           if( !_cmds.empty() && !_coords.empty() )
450             vgAppendPathData(_path, _cmds.size(), &_cmds[0], &_coords[0]);
451
452           _attributes_dirty &= ~PATH;
453           _attributes_dirty |= BOUNDING_BOX;
454         }
455
456         if( _attributes_dirty & BOUNDING_BOX )
457           dirtyBound();
458       }
459
460       struct PathUpdateCallback:
461         public osg::Drawable::UpdateCallback
462       {
463         virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
464         {
465           static_cast<PathDrawable*>(drawable)->update();
466         }
467       };
468   };
469
470   //----------------------------------------------------------------------------
471   Path::Path( const CanvasWeakPtr& canvas,
472               const SGPropertyNode_ptr& node,
473               const Style& parent_style,
474               Element* parent ):
475     Element(canvas, node, parent_style, parent),
476     _path( new PathDrawable(this) )
477   {
478     setDrawable(_path);
479     PathDrawable *path = _path.get();
480
481     addStyle("fill", &PathDrawable::setFill, path);
482     addStyle("fill-rule", &PathDrawable::setFillRule, path);
483     addStyle("stroke", &PathDrawable::setStroke, path);
484     addStyle("stroke-width", &PathDrawable::setStrokeWidth, path);
485     addStyle("stroke-dasharray", &PathDrawable::setStrokeDashArray, path);
486     addStyle("stroke-linecap", &PathDrawable::setStrokeLinecap, path);
487
488     setupStyle();
489   }
490
491   //----------------------------------------------------------------------------
492   Path::~Path()
493   {
494
495   }
496
497   //----------------------------------------------------------------------------
498   void Path::update(double dt)
499   {
500     if( _attributes_dirty & (CMDS | COORDS) )
501     {
502       _path->setSegments
503       (
504         _node->getChildValues<VGubyte, int>("cmd"),
505         _node->getChildValues<VGfloat, float>("coord")
506       );
507
508       _attributes_dirty &= ~(CMDS | COORDS);
509     }
510
511     Element::update(dt);
512   }
513
514   //----------------------------------------------------------------------------
515   osg::BoundingBox Path::getTransformedBounds(const osg::Matrix& m) const
516   {
517     return _path->getTransformedBounds(m);
518   }
519
520   //----------------------------------------------------------------------------
521   void Path::childRemoved(SGPropertyNode* child)
522   {
523     childChanged(child);
524   }
525
526   //----------------------------------------------------------------------------
527   void Path::childChanged(SGPropertyNode* child)
528   {
529     if( child->getParent() != _node )
530       return;
531
532     if( child->getNameString() == "cmd" )
533       _attributes_dirty |= CMDS;
534     else if( child->getNameString() == "coord" )
535       _attributes_dirty |= COORDS;
536   }
537
538   //----------------------------------------------------------------------------
539   std::vector<float> splitAndConvert(const char del[], const std::string& str)
540   {
541     std::vector<float> values;
542     size_t pos = 0;
543     for(;;)
544     {
545       pos = str.find_first_not_of(del, pos);
546       if( pos == std::string::npos )
547         break;
548
549       char *end = 0;
550       float val = strtod(&str[pos], &end);
551       if( end == &str[pos] || !end )
552         break;
553
554       values.push_back(val);
555       pos = end - &str[0];
556     }
557     return values;
558   }
559
560 } // namespace canvas
561 } // namespace simgear