1 // An OpenVG path on the Canvas
3 // Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
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.
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.
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
19 #include "CanvasPath.hxx"
20 #include <simgear/scene/util/parse_color.hxx>
22 #include <osg/Drawable>
23 #include <osg/BlendFunc>
25 #include <vg/openvg.h>
32 typedef std::vector<VGubyte> CmdList;
33 typedef std::vector<VGfloat> CoordList;
36 * Helper to split and convert comma/whitespace separated floating point
39 std::vector<float> splitAndConvert(const char del[], const std::string& str);
41 class Path::PathDrawable:
45 PathDrawable(Path* path):
47 _path(VG_INVALID_HANDLE),
48 _paint(VG_INVALID_HANDLE),
49 _paint_fill(VG_INVALID_HANDLE),
50 _attributes_dirty(~0),
52 _fill_rule(VG_EVEN_ODD),
54 _stroke_linecap(VG_CAP_BUTT)
56 setSupportsDisplayList(false);
57 setDataVariance(Object::DYNAMIC);
59 setUpdateCallback(new PathUpdateCallback());
62 virtual ~PathDrawable()
64 if( _path != VG_INVALID_HANDLE )
66 if( _paint != VG_INVALID_HANDLE )
67 vgDestroyPaint(_paint);
68 if( _paint_fill != VG_INVALID_HANDLE )
69 vgDestroyPaint(_paint_fill);
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); }
77 * Replace the current path segments with the new ones
79 * @param cmds List of OpenVG path commands
80 * @param coords List of coordinates/parameters used by #cmds
82 void setSegments(const CmdList& cmds, const CoordList& coords)
87 _attributes_dirty |= (PATH | BOUNDING_BOX);
91 * Set path fill paint ("none" if not filled)
93 void setFill(const std::string& fill)
97 _mode &= ~VG_FILL_PATH;
99 else if( parseColor(fill, _fill_color) )
101 _mode |= VG_FILL_PATH;
102 _attributes_dirty |= FILL_COLOR;
110 "canvas::Path Unknown fill: " << fill
116 * Set path fill rule ("pseudo-nonzero" or "evenodd")
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 :)
122 void setFillRule(const std::string& fill_rule)
124 if( fill_rule == "pseudo-nonzero" )
125 _fill_rule = VG_NON_ZERO;
126 else // if( fill_rule == "evenodd" )
127 _fill_rule = VG_EVEN_ODD;
131 * Set path stroke paint ("none" if no stroke)
133 void setStroke(const std::string& stroke)
135 if( stroke == "none" )
137 _mode &= ~VG_STROKE_PATH;
139 else if( parseColor(stroke, _stroke_color) )
141 _mode |= VG_STROKE_PATH;
142 _attributes_dirty |= STROKE_COLOR;
150 "canvas::Path Unknown stroke: " << stroke
158 void setStrokeWidth(float width)
160 _stroke_width = width;
161 _attributes_dirty |= BOUNDING_BOX;
165 * Set stroke dash (line stipple)
167 void setStrokeDashArray(const std::string& dash)
169 _stroke_dash = splitAndConvert(",\t\n ", dash);
175 * @see http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty
177 void setStrokeLinecap(const std::string& linecap)
179 if( linecap == "round" )
180 _stroke_linecap = VG_CAP_ROUND;
181 else if( linecap == "square" )
182 _stroke_linecap = VG_CAP_SQUARE;
184 _stroke_linecap = VG_CAP_BUTT;
190 virtual void drawImplementation(osg::RenderInfo& renderInfo) const
192 if( _attributes_dirty & PATH )
195 osg::State* state = renderInfo.getState();
198 state->setActiveTextureUnit(0);
199 state->setClientActiveTextureUnit(0);
200 state->disableAllVertexArrays();
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);
206 // Initialize/Update the paint
207 if( _attributes_dirty & STROKE_COLOR )
209 if( _paint == VG_INVALID_HANDLE )
210 _paint = vgCreatePaint();
212 vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, _stroke_color._v);
214 _attributes_dirty &= ~STROKE_COLOR;
217 // Initialize/update fill paint
218 if( _attributes_dirty & FILL_COLOR )
220 if( _paint_fill == VG_INVALID_HANDLE )
221 _paint_fill = vgCreatePaint();
223 vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, _fill_color._v);
225 _attributes_dirty &= ~FILL_COLOR;
229 if( _mode & VG_STROKE_PATH )
231 vgSetPaint(_paint, VG_STROKE_PATH);
233 vgSetf(VG_STROKE_LINE_WIDTH, _stroke_width);
234 vgSeti(VG_STROKE_CAP_STYLE, _stroke_linecap);
235 vgSetfv( VG_STROKE_DASH_PATTERN,
237 _stroke_dash.empty() ? 0 : &_stroke_dash[0] );
239 if( _mode & VG_FILL_PATH )
241 vgSetPaint(_paint_fill, VG_FILL_PATH);
243 vgSeti(VG_FILL_RULE, _fill_rule);
246 // And finally draw the path
248 vgDrawPath(_path, _mode);
250 VGErrorCode err = vgGetError();
251 if( err != VG_NO_ERROR )
252 SG_LOG(SG_GL, SG_ALERT, "vgError: " << err);
259 * Compute the bounding box
261 virtual osg::BoundingBox computeBound() const
263 if( _path == VG_INVALID_HANDLE || (_attributes_dirty & PATH) )
264 return osg::BoundingBox();
266 VGfloat min[2], size[2];
267 vgPathBounds(_path, &min[0], &min[1], &size[0], &size[1]);
269 _attributes_dirty &= ~BOUNDING_BOX;
271 // vgPathBounds doesn't take stroke width into account
272 float ext = 0.5 * _stroke_width;
276 min[0] - ext, min[1] - ext, -0.1,
277 min[0] + size[0] + ext, min[1] + size[1] + ext, 0.1
279 _path_element->setBoundingBox(bb);
289 STROKE_COLOR = PATH << 1,
290 FILL_COLOR = STROKE_COLOR << 1,
291 BOUNDING_BOX = FILL_COLOR << 1
296 mutable VGPath _path;
297 mutable VGPaint _paint;
298 mutable VGPaint _paint_fill;
299 mutable uint32_t _attributes_dirty;
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;
313 * Initialize/Update the OpenVG path
317 if( _attributes_dirty & PATH )
319 const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
320 | VG_PATH_CAPABILITY_MODIFY
321 | VG_PATH_CAPABILITY_PATH_BOUNDS;
323 if( _path == VG_INVALID_HANDLE )
324 _path = vgCreatePath(
325 VG_PATH_FORMAT_STANDARD,
327 1.f, 0.f, // scale,bias
328 _cmds.size(), _coords.size(),
332 vgClearPath(_path, caps);
334 if( !_cmds.empty() && !_coords.empty() )
335 vgAppendPathData(_path, _cmds.size(), &_cmds[0], &_coords[0]);
337 _attributes_dirty &= ~PATH;
338 _attributes_dirty |= BOUNDING_BOX;
341 if( _attributes_dirty & BOUNDING_BOX )
345 struct PathUpdateCallback:
346 public osg::Drawable::UpdateCallback
348 virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
350 static_cast<PathDrawable*>(drawable)->update();
355 //----------------------------------------------------------------------------
356 Path::Path( const CanvasWeakPtr& canvas,
357 const SGPropertyNode_ptr& node,
358 const Style& parent_style,
360 Element(canvas, node, parent_style, parent),
361 _path( new PathDrawable(this) )
364 PathDrawable *path = _path.get();
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);
376 //----------------------------------------------------------------------------
382 //----------------------------------------------------------------------------
383 void Path::update(double dt)
385 if( _attributes_dirty & (CMDS | COORDS) )
389 _node->getChildValues<VGubyte, int>("cmd"),
390 _node->getChildValues<VGfloat, float>("coord")
393 _attributes_dirty &= ~(CMDS | COORDS);
399 //----------------------------------------------------------------------------
400 void Path::childRemoved(SGPropertyNode* child)
405 //----------------------------------------------------------------------------
406 void Path::childChanged(SGPropertyNode* child)
408 if( child->getParent() != _node )
411 if( child->getNameString() == "cmd" )
412 _attributes_dirty |= CMDS;
413 else if( child->getNameString() == "coord" )
414 _attributes_dirty |= COORDS;
417 //----------------------------------------------------------------------------
418 std::vector<float> splitAndConvert(const char del[], const std::string& str)
420 std::vector<float> values;
424 pos = str.find_first_not_of(del, pos);
425 if( pos == std::string::npos )
429 float val = strtod(&str[pos], &end);
430 if( end == &str[pos] || !end )
433 values.push_back(val);
439 } // namespace canvas
440 } // namespace simgear