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