]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasPath.cxx
a2d0c1a57be24c3b7a4b90485b4f8b5466e0e03a
[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/Version>
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         _stroke_linejoin(VG_JOIN_MITER)
75       {
76         setSupportsDisplayList(false);
77         setDataVariance(Object::DYNAMIC);
78
79         setUpdateCallback(new PathUpdateCallback());
80       }
81
82       virtual ~PathDrawable()
83       {
84         if( _path != VG_INVALID_HANDLE )
85           vgDestroyPath(_path);
86         if( _paint != VG_INVALID_HANDLE )
87           vgDestroyPaint(_paint);
88         if( _paint_fill != VG_INVALID_HANDLE )
89           vgDestroyPaint(_paint_fill);
90       }
91
92       virtual const char* className() const { return "PathDrawable"; }
93       virtual osg::Object* cloneType() const { return new PathDrawable(_path_element); }
94       virtual osg::Object* clone(const osg::CopyOp&) const { return new PathDrawable(_path_element); }
95
96       /**
97        * Replace the current path segments with the new ones
98        *
99        * @param cmds    List of OpenVG path commands
100        * @param coords  List of coordinates/parameters used by #cmds
101        */
102       void setSegments(const CmdList& cmds, const CoordList& coords)
103       {
104         _cmds = cmds;
105         _coords = coords;
106
107         _attributes_dirty |= (PATH | BOUNDING_BOX);
108       }
109
110       /**
111        * Set path fill paint ("none" if not filled)
112        */
113       void setFill(const std::string& fill)
114       {
115         if( fill.empty() || fill == "none" )
116         {
117           _mode &= ~VG_FILL_PATH;
118         }
119         else if( parseColor(fill, _fill_color) )
120         {
121           _mode |= VG_FILL_PATH;
122           _attributes_dirty |= FILL_COLOR;
123         }
124         else
125         {
126           SG_LOG
127           (
128             SG_GENERAL,
129             SG_WARN,
130             "canvas::Path Unknown fill: " << fill
131           );
132         }
133       }
134
135       /**
136        * Set path fill rule ("pseudo-nonzero" or "evenodd")
137        *
138        * @warning As the current nonzero implementation causes sever artifacts
139        *          for every concave path we call it pseudo-nonzero, so that
140        *          everyone is warned that it won't work as expected :)
141        */
142       void setFillRule(const std::string& fill_rule)
143       {
144         if( fill_rule == "pseudo-nonzero" )
145           _fill_rule = VG_NON_ZERO;
146         else // if( fill_rule == "evenodd" )
147           _fill_rule = VG_EVEN_ODD;
148       }
149
150       /**
151        * Set path stroke paint ("none" if no stroke)
152        */
153       void setStroke(const std::string& stroke)
154       {
155         if( stroke.empty() || stroke == "none" )
156         {
157           _mode &= ~VG_STROKE_PATH;
158         }
159         else if( parseColor(stroke, _stroke_color) )
160         {
161           _mode |= VG_STROKE_PATH;
162                     _attributes_dirty |= STROKE_COLOR;
163         }
164         else
165         {
166           SG_LOG
167           (
168             SG_GENERAL,
169             SG_WARN,
170             "canvas::Path Unknown stroke: " << stroke
171           );
172         }
173       }
174
175       /**
176        * Set stroke width
177        */
178       void setStrokeWidth(float width)
179       {
180         _stroke_width = width;
181         _attributes_dirty |= BOUNDING_BOX;
182       }
183
184       /**
185        * Set stroke dash (line stipple)
186        */
187       void setStrokeDashArray(const std::string& dash)
188       {
189         _stroke_dash = splitAndConvert(",\t\n ", dash);
190       }
191
192       /**
193        * Set stroke-linecap
194        *
195        * @see http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty
196        */
197       void setStrokeLinecap(const std::string& linecap)
198       {
199         if( linecap == "round" )
200           _stroke_linecap = VG_CAP_ROUND;
201         else if( linecap == "square" )
202           _stroke_linecap = VG_CAP_SQUARE;
203         else
204           _stroke_linecap = VG_CAP_BUTT;
205       }
206
207       /**
208        * Set stroke-linejoin
209        *
210        * @see http://www.w3.org/TR/SVG/painting.html#StrokeLinejoinProperty
211        */
212       void setStrokeLinejoin(const std::string& linejoin)
213       {
214         if( linejoin == "round" )
215           _stroke_linejoin = VG_JOIN_ROUND;
216         else if( linejoin == "bevel" )
217           _stroke_linejoin = VG_JOIN_BEVEL;
218         else
219           _stroke_linejoin = VG_JOIN_MITER;
220       }
221
222       /**
223        * Draw callback
224        */
225       virtual void drawImplementation(osg::RenderInfo& renderInfo) const
226       {
227         if( _attributes_dirty & PATH )
228           return;
229
230         osg::State* state = renderInfo.getState();
231         assert(state);
232
233         state->setActiveTextureUnit(0);
234         state->setClientActiveTextureUnit(0);
235         state->disableAllVertexArrays();
236
237         bool was_blend_enabled = state->getLastAppliedMode(GL_BLEND);
238         bool was_stencil_enabled = state->getLastAppliedMode(GL_STENCIL_TEST);
239         osg::StateAttribute const* blend_func =
240           state->getLastAppliedAttribute(osg::StateAttribute::BLENDFUNC);
241
242         // Initialize/Update the paint
243         if( _attributes_dirty & STROKE_COLOR )
244         {
245           if( _paint == VG_INVALID_HANDLE )
246             _paint = vgCreatePaint();
247
248           vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, _stroke_color._v);
249
250           _attributes_dirty &= ~STROKE_COLOR;
251         }
252
253         // Initialize/update fill paint
254         if( _attributes_dirty & FILL_COLOR )
255         {
256           if( _paint_fill == VG_INVALID_HANDLE )
257             _paint_fill = vgCreatePaint();
258
259           vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, _fill_color._v);
260
261           _attributes_dirty &= ~FILL_COLOR;
262         }
263
264         // Setup paint
265         if( _mode & VG_STROKE_PATH )
266         {
267           vgSetPaint(_paint, VG_STROKE_PATH);
268
269           vgSetf(VG_STROKE_LINE_WIDTH, _stroke_width);
270           vgSeti(VG_STROKE_CAP_STYLE, _stroke_linecap);
271           vgSeti(VG_STROKE_JOIN_STYLE, _stroke_linejoin);
272           vgSetfv( VG_STROKE_DASH_PATTERN,
273                    _stroke_dash.size(),
274                    _stroke_dash.empty() ? 0 : &_stroke_dash[0] );
275         }
276         if( _mode & VG_FILL_PATH )
277         {
278           vgSetPaint(_paint_fill, VG_FILL_PATH);
279
280           vgSeti(VG_FILL_RULE, _fill_rule);
281         }
282
283         // And finally draw the path
284         if( _mode )
285           vgDrawPath(_path, _mode);
286
287         VGErrorCode err = vgGetError();
288         if( err != VG_NO_ERROR )
289           SG_LOG(SG_GL, SG_ALERT, "vgError: " << err);
290
291         // Restore OpenGL state (TODO check if more is needed or integrate
292         //                            better with OpenSceneGraph)
293         if( was_blend_enabled )   glEnable(GL_BLEND);
294         if( was_stencil_enabled ) glEnable(GL_STENCIL_TEST);
295         if( blend_func )          blend_func->apply(*state);
296       }
297
298       osg::BoundingBox getTransformedBounds(const osg::Matrix& mat) const
299       {
300         osg::BoundingBox bb;
301
302         osg::Vec2f cur(0, 0), // VG "Current point" (in local coordinates)
303                    sub(0, 0); // beginning of current sub path
304         VGubyte cmd_index = 0;
305         for( size_t i = 0,              ci = 0;
306                     i < _cmds.size() && ci < _coords.size();
307                   ++i,                  ci += shCoordsPerCommand[cmd_index] )
308         {
309           VGubyte rel = _cmds[i] & 1,
310                   cmd = _cmds[i] & ~1;
311
312           cmd_index = cmd / 2;
313           if( cmd_index >= shNumCommands )
314             return osg::BoundingBox();
315
316           const VGubyte max_coords = 3;
317           osg::Vec2f points[max_coords];
318           VGubyte num_coords = 0;
319
320           switch( cmd )
321           {
322             case VG_CLOSE_PATH:
323               cur = sub;
324               break;
325             case VG_MOVE_TO:
326             case VG_LINE_TO:
327             case VG_SQUAD_TO:
328               // x0, y0
329               points[ num_coords++ ].set(_coords[ci], _coords[ci + 1]);
330               break;
331             case VG_HLINE_TO:
332               // x0
333               points[ num_coords++ ].set( _coords[ci] + (rel ? cur.x() : 0),
334                                           cur.y() );
335               // We have handled rel/abs already, so no need to do it again...
336               rel = 0;
337               break;
338             case VG_VLINE_TO:
339               // y0
340               points[ num_coords++ ].set( cur.x(),
341                                           _coords[ci] + (rel ? cur.y() : 0) );
342               // We have handled rel/abs already, so no need to do it again...
343               rel = 0;
344               break;
345             case VG_QUAD_TO:
346             case VG_SCUBIC_TO:
347               // x0,y0,x1,y1
348               points[ num_coords++ ].set(_coords[ci    ], _coords[ci + 1]);
349               points[ num_coords++ ].set(_coords[ci + 2], _coords[ci + 3]);
350               break;
351             case VG_CUBIC_TO:
352               // x0,y0,x1,y1,x2,y2
353               points[ num_coords++ ].set(_coords[ci    ], _coords[ci + 1]);
354               points[ num_coords++ ].set(_coords[ci + 2], _coords[ci + 3]);
355               points[ num_coords++ ].set(_coords[ci + 4], _coords[ci + 5]);
356               break;
357             case VG_SCCWARC_TO:
358             case VG_SCWARC_TO:
359             case VG_LCCWARC_TO:
360             case VG_LCWARC_TO:
361               // rh,rv,rot,x0,y0
362               points[ num_coords++ ].set(_coords[ci + 3], _coords[ci + 4]);
363               break;
364             default:
365               SG_LOG(SG_GL, SG_WARN, "Unknown VG command: " << (int)cmd);
366               return osg::BoundingBox();
367           }
368
369           assert(num_coords <= max_coords);
370           for(VGubyte i = 0; i < num_coords; ++i)
371           {
372             if( rel )
373               points[i] += cur;
374
375             bb.expandBy( transformPoint(mat, points[i]) );
376           }
377
378           if( num_coords > 0 )
379           {
380             cur = points[ num_coords - 1 ];
381
382             if( cmd == VG_MOVE_TO )
383               sub = cur;
384           }
385         }
386
387         return bb;
388       }
389
390       /**
391        * Compute the bounding box
392        */
393       virtual osg::BoundingBox
394 #if OSG_VERSION_LESS_THAN(3,3,2)
395       computeBound()
396 #else
397       computeBoundingBox()
398 #endif
399       const
400       {
401         if( _path == VG_INVALID_HANDLE || (_attributes_dirty & PATH) )
402           return osg::BoundingBox();
403
404         VGfloat min[2], size[2];
405         vgPathBounds(_path, &min[0], &min[1], &size[0], &size[1]);
406
407         _attributes_dirty &= ~BOUNDING_BOX;
408
409         // vgPathBounds doesn't take stroke width into account
410         float ext = 0.5 * _stroke_width;
411
412         return osg::BoundingBox
413         (
414           min[0] - ext,           min[1] - ext,           -0.1,
415           min[0] + size[0] + ext, min[1] + size[1] + ext,  0.1
416         );
417       }
418
419     private:
420
421       enum Attributes
422       {
423         PATH            = 0x0001,
424         STROKE_COLOR    = PATH << 1,
425         FILL_COLOR      = STROKE_COLOR << 1,
426         BOUNDING_BOX    = FILL_COLOR << 1
427       };
428
429       Path *_path_element;
430
431       mutable VGPath    _path;
432       mutable VGPaint   _paint;
433       mutable VGPaint   _paint_fill;
434       mutable uint32_t  _attributes_dirty;
435
436       CmdList   _cmds;
437       CoordList _coords;
438
439       VGbitfield            _mode;
440       osg::Vec4f            _fill_color;
441       VGFillRule            _fill_rule;
442       osg::Vec4f            _stroke_color;
443       VGfloat               _stroke_width;
444       std::vector<VGfloat>  _stroke_dash;
445       VGCapStyle            _stroke_linecap;
446       VGJoinStyle            _stroke_linejoin;
447
448       osg::Vec3f transformPoint( const osg::Matrix& m,
449                                  osg::Vec2f pos ) const
450       {
451         return osg::Vec3
452         (
453           m(0, 0) * pos[0] + m(1, 0) * pos[1] + m(3, 0),
454           m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1),
455           0
456         );
457       }
458
459       /**
460        * Initialize/Update the OpenVG path
461        */
462       void update()
463       {
464         if( !vgHasContextSH() )
465           return;
466
467         if( _attributes_dirty & PATH )
468         {
469           const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
470                                 | VG_PATH_CAPABILITY_MODIFY
471                                 | VG_PATH_CAPABILITY_PATH_BOUNDS;
472
473           if( _path == VG_INVALID_HANDLE )
474             _path = vgCreatePath(
475               VG_PATH_FORMAT_STANDARD,
476               VG_PATH_DATATYPE_F,
477               1.f, 0.f, // scale,bias
478               _cmds.size(), _coords.size(),
479               caps
480             );
481           else
482             vgClearPath(_path, caps);
483
484           if( !_cmds.empty() && !_coords.empty() )
485             vgAppendPathData(_path, _cmds.size(), &_cmds[0], &_coords[0]);
486
487           _attributes_dirty &= ~PATH;
488           _attributes_dirty |= BOUNDING_BOX;
489         }
490
491         if( _attributes_dirty & BOUNDING_BOX )
492         {
493           dirtyBound();
494
495           // Recalculate bounding box now (prevent race condition)
496           getBound();
497         }
498       }
499
500       struct PathUpdateCallback:
501         public osg::Drawable::UpdateCallback
502       {
503         virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
504         {
505           static_cast<PathDrawable*>(drawable)->update();
506         }
507       };
508   };
509
510   //----------------------------------------------------------------------------
511   const std::string Path::TYPE_NAME = "path";
512
513   //----------------------------------------------------------------------------
514   void Path::staticInit()
515   {
516     if( isInit<Path>() )
517       return;
518
519     PathDrawableRef Path::*path = &Path::_path;
520
521     addStyle("fill", "color", &PathDrawable::setFill, path);
522     addStyle("fill-rule", "", &PathDrawable::setFillRule, path);
523     addStyle("stroke", "color", &PathDrawable::setStroke, path);
524     addStyle("stroke-width", "numeric", &PathDrawable::setStrokeWidth, path);
525     addStyle("stroke-dasharray", "", &PathDrawable::setStrokeDashArray, path);
526     addStyle("stroke-linecap", "", &PathDrawable::setStrokeLinecap, path);
527     addStyle("stroke-linejoin", "", &PathDrawable::setStrokeLinejoin, path);
528   }
529
530   //----------------------------------------------------------------------------
531   Path::Path( const CanvasWeakPtr& canvas,
532               const SGPropertyNode_ptr& node,
533               const Style& parent_style,
534               Element* parent ):
535     Element(canvas, node, parent_style, parent),
536     _path( new PathDrawable(this) )
537   {
538     staticInit();
539
540     setDrawable(_path);
541     setupStyle();
542   }
543
544   //----------------------------------------------------------------------------
545   Path::~Path()
546   {
547
548   }
549
550   //----------------------------------------------------------------------------
551   void Path::update(double dt)
552   {
553     if( _attributes_dirty & (CMDS | COORDS) )
554     {
555       _path->setSegments
556       (
557         _node->getChildValues<VGubyte, int>("cmd"),
558         _node->getChildValues<VGfloat, float>("coord")
559       );
560
561       _attributes_dirty &= ~(CMDS | COORDS);
562     }
563
564     Element::update(dt);
565   }
566
567   //----------------------------------------------------------------------------
568   osg::BoundingBox Path::getTransformedBounds(const osg::Matrix& m) const
569   {
570     return _path->getTransformedBounds(m);
571   }
572
573   //----------------------------------------------------------------------------
574   Path& Path::moveTo(float x_abs, float y_abs)
575   {
576     return addSegment(VG_MOVE_TO_ABS, x_abs, y_abs);
577   }
578
579   //----------------------------------------------------------------------------
580   Path& Path::move(float x_rel, float y_rel)
581   {
582     return addSegment(VG_MOVE_TO_REL, x_rel, y_rel);
583   }
584
585   //----------------------------------------------------------------------------
586   Path& Path::lineTo(float x_abs, float y_abs)
587   {
588     return addSegment(VG_LINE_TO_ABS, x_abs, y_abs);
589   }
590
591   //----------------------------------------------------------------------------
592   Path& Path::line(float x_rel, float y_rel)
593   {
594     return addSegment(VG_LINE_TO_REL, x_rel, y_rel);
595   }
596
597   //----------------------------------------------------------------------------
598   Path& Path::horizTo(float x_abs)
599   {
600     return addSegment(VG_HLINE_TO_ABS, x_abs);
601   }
602
603   //----------------------------------------------------------------------------
604   Path& Path::horiz(float x_rel)
605   {
606     return addSegment(VG_HLINE_TO_REL, x_rel);
607   }
608
609   //----------------------------------------------------------------------------
610   Path& Path::vertTo(float y_abs)
611   {
612     return addSegment(VG_VLINE_TO_ABS, y_abs);
613   }
614
615   //----------------------------------------------------------------------------
616   Path& Path::vert(float y_rel)
617   {
618     return addSegment(VG_VLINE_TO_REL, y_rel);
619   }
620
621   //----------------------------------------------------------------------------
622   Path& Path::close()
623   {
624     return addSegment(VG_CLOSE_PATH);
625   }
626
627   //----------------------------------------------------------------------------
628   void Path::childRemoved(SGPropertyNode* child)
629   {
630     childChanged(child);
631   }
632
633   //----------------------------------------------------------------------------
634   void Path::childChanged(SGPropertyNode* child)
635   {
636     if( child->getParent() != _node )
637       return;
638
639     if( child->getNameString() == "cmd" )
640       _attributes_dirty |= CMDS;
641     else if( child->getNameString() == "coord" )
642       _attributes_dirty |= COORDS;
643   }
644
645   //----------------------------------------------------------------------------
646   std::vector<float> splitAndConvert(const char del[], const std::string& str)
647   {
648     std::vector<float> values;
649     size_t pos = 0;
650     for(;;)
651     {
652       pos = str.find_first_not_of(del, pos);
653       if( pos == std::string::npos )
654         break;
655
656       char *end = 0;
657       float val = strtod(&str[pos], &end);
658       if( end == &str[pos] || !end )
659         break;
660
661       values.push_back(val);
662       pos = end - &str[0];
663     }
664     return values;
665   }
666
667 } // namespace canvas
668 } // namespace simgear