esvg/esvg/render/Path.cpp

520 lines
19 KiB
C++

/** @file
* @author Edouard DUPIN
*
* @copyright 2011, Edouard DUPIN, all right reserved
*
* @license APACHE v2.0 (see license file)
*/
#include <esvg/debug.h>
#include <esvg/render/Path.h>
#include <esvg/render/Element.h>
void esvg::render::Path::clear() {
m_listElement.clear();
}
void esvg::render::Path::stop() {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementStop>());
}
void esvg::render::Path::close(bool _relative) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementClose>(_relative));
}
void esvg::render::Path::moveTo(bool _relative, const vec2& _pos) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementMoveTo>(_relative, _pos));
}
void esvg::render::Path::lineTo(bool _relative, const vec2& _pos) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementLineTo>(_relative, _pos));
}
void esvg::render::Path::lineToH(bool _relative, float _posX) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementLineToH>(_relative, _posX));
}
void esvg::render::Path::lineToV(bool _relative, float _posY) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementLineToV>(_relative, _posY));
}
void esvg::render::Path::curveTo(bool _relative, const vec2& _pos1, const vec2& _pos2, const vec2& _pos) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementCurveTo>(_relative, _pos1, _pos2, _pos));
}
void esvg::render::Path::smoothCurveTo(bool _relative, const vec2& _pos2, const vec2& _pos) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementSmoothCurveTo>(_relative, _pos2, _pos));
}
void esvg::render::Path::bezierCurveTo(bool _relative, const vec2& _pos1, const vec2& _pos) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementBezierCurveTo>(_relative, _pos1, _pos));
}
void esvg::render::Path::bezierSmoothCurveTo(bool _relative, const vec2& _pos) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementBezierSmoothCurveTo>(_relative, _pos));
}
void esvg::render::Path::ellipticTo(bool _relative,
const vec2& _radius,
float _angle,
bool _largeArcFlag,
bool _sweepFlag,
const vec2& _pos) {
m_listElement.push_back(ememory::makeShared<esvg::render::ElementElliptic>(_relative, _radius, _angle, _largeArcFlag, _sweepFlag, _pos));
}
static const char* spacingDist(int32_t _spacing) {
static const char *tmpValue = " ";
if (_spacing>20) {
_spacing = 20;
}
return tmpValue + 20*4 - _spacing*4;
}
void esvg::render::Path::display(int32_t _spacing) {
ESVG_DEBUG(spacingDist(_spacing) << "Path");
for(auto &it : m_listElement) {
if (it == nullptr) {
continue;
}
ESVG_DEBUG(spacingDist(_spacing+1) << *it);
}
}
void interpolateCubicBezier(std::vector<esvg::render::Point>& _listPoint,
int32_t _recurtionMax,
float _threshold,
vec2 _pos1,
vec2 _pos2,
vec2 _pos3,
vec2 _pos4,
int32_t _level,
enum esvg::render::Point::type _type) {
if (_level > _recurtionMax) {
return;
}
vec2 pos12 = (_pos1+_pos2)*0.5f;
vec2 pos23 = (_pos2+_pos3)*0.5f;
vec2 pos34 = (_pos3+_pos4)*0.5f;
vec2 delta = _pos4 - _pos1;
#ifndef __STDCPP_LLVM__
float distance2 = std::abs(((_pos2.x() - _pos4.x()) * delta.y() - (_pos2.y() - _pos4.y()) * delta.x() ));
float distance3 = std::abs(((_pos3.x() - _pos4.x()) * delta.y() - (_pos3.y() - _pos4.y()) * delta.x() ));
#else
float distance2 = fabs(((_pos2.x() - _pos4.x()) * delta.y() - (_pos2.y() - _pos4.y()) * delta.x() ));
float distance3 = fabs(((_pos3.x() - _pos4.x()) * delta.y() - (_pos3.y() - _pos4.y()) * delta.x() ));
#endif
if ((distance2 + distance3)*(distance2 + distance3) < _threshold * delta.length2()) {
_listPoint.push_back(esvg::render::Point(_pos4, _type) );
return;
}
vec2 pos123 = (pos12+pos23)*0.5f;
vec2 pos234 = (pos23+pos34)*0.5f;
vec2 pos1234 = (pos123+pos234)*0.5f;
interpolateCubicBezier(_listPoint, _recurtionMax, _threshold, _pos1, pos12, pos123, pos1234, _level+1, esvg::render::Point::type::interpolation);
interpolateCubicBezier(_listPoint, _recurtionMax, _threshold, pos1234, pos234, pos34, _pos4, _level+1, _type);
}
static float vectorAngle(vec2 _uuu, vec2 _vvv) {
_uuu.safeNormalize();
_vvv.safeNormalize();
return atan2(_uuu.cross(_vvv), _uuu.dot(_vvv));
}
esvg::render::PointList esvg::render::Path::generateListPoints(int32_t _level, int32_t _recurtionMax, float _threshold) {
ESVG_VERBOSE(spacingDist(_level) << "Generate List Points ... from a path");
esvg::render::PointList out;
std::vector<esvg::render::Point> tmpListPoint;
vec2 lastPosition(0.0f, 0.0f);
vec2 lastAngle(0.0f, 0.0f);
int32_t lastPointId = -1;
bool PathStart = false;
// Foreach element, we move in the path:
for(auto &it : m_listElement) {
if (it == nullptr) {
continue;
}
ESVG_VERBOSE(spacingDist(_level+1) << " Draw : " << *it);
switch (it->getType()) {
case esvg::render::path_stop:
if (tmpListPoint.size() != 0) {
if (tmpListPoint.size() == 0) {
ESVG_WARNING(spacingDist(_level+1) << " Request path stop of not starting path ...");
} else {
tmpListPoint.back().setEndPath();
out.addList(tmpListPoint);
tmpListPoint.clear();
}
}
lastAngle = vec2(0.0f, 0.0f);
// nothing alse to do ...
break;
case esvg::render::path_close:
if (tmpListPoint.size() != 0) {
if (tmpListPoint.size() == 0) {
ESVG_WARNING(spacingDist(_level+1) << " Request path close of not starting path ...");
} else {
// find the previous tart of the path ...
tmpListPoint.front().m_type = esvg::render::Point::type::join;
// Remove the last point if it is the same position...
vec2 delta = (tmpListPoint.front().m_pos - tmpListPoint.back().m_pos).absolute();
if ( delta.x() <= 0.00001
&& delta.y() <= 0.00001) {
tmpListPoint.pop_back();
ESVG_VERBOSE(" Remove point Z property : " << tmpListPoint.back().m_pos << " with delta=" << delta);
}
out.addList(tmpListPoint);
tmpListPoint.clear();
}
}
lastAngle = vec2(0.0f, 0.0f);
// nothing alse to do ...
break;
case esvg::render::path_moveTo:
// stop last path
if (tmpListPoint.size() != 0) {
tmpListPoint.back().setEndPath();
out.addList(tmpListPoint);
tmpListPoint.clear();
}
// create a new one
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
lastPosition += it->getPos();
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::start));
lastAngle = lastPosition;
break;
case esvg::render::path_lineTo:
// If no previous point, we need to create the last point has start ...
if (tmpListPoint.size() == 0) {
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::start));
}
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
lastPosition += it->getPos();
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::join));
lastAngle = lastPosition;
break;
case esvg::render::path_lineToH:
// If no previous point, we need to create the last point has start ...
if (tmpListPoint.size() == 0) {
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::start));
}
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
lastPosition += it->getPos();
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::join));
lastAngle = lastPosition;
break;
case esvg::render::path_lineToV:
// If no previous point, we need to create the last point has start ...
if (tmpListPoint.size() == 0) {
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::start));
}
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
lastPosition += it->getPos();
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::join));
lastAngle = lastPosition;
break;
case esvg::render::path_curveTo:
// If no previous point, we need to create the last point has start ...
if (tmpListPoint.size() == 0) {
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::join));
}
{
vec2 lastPosStore(lastPosition);
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
vec2 pos1 = lastPosition + it->getPos1();
vec2 pos2 = lastPosition + it->getPos2();
vec2 pos = lastPosition + it->getPos();
interpolateCubicBezier(tmpListPoint,
_recurtionMax,
_threshold,
lastPosStore,
pos1,
pos2,
pos,
0,
esvg::render::Point::type::join);
lastPosition = pos;
lastAngle = pos2;
}
break;
case esvg::render::path_smoothCurveTo:
// If no previous point, we need to create the last point has start ...
if (tmpListPoint.size() == 0) {
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::join));
}
{
vec2 lastPosStore(lastPosition);
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
vec2 pos2 = lastPosition + it->getPos2();
vec2 pos = lastPosition + it->getPos();
// generate Pos 1
vec2 pos1 = lastPosStore*2.0f - lastAngle;
interpolateCubicBezier(tmpListPoint,
_recurtionMax,
_threshold,
lastPosStore,
pos1,
pos2,
pos,
0,
esvg::render::Point::type::join);
lastPosition = pos;
lastAngle = pos2;
}
break;
case esvg::render::path_bezierCurveTo:
// If no previous point, we need to create the last point has start ...
if (tmpListPoint.size() == 0) {
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::join));
}
{
vec2 lastPosStore(lastPosition);
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
vec2 pos = lastPosition + it->getPos();
vec2 tmp1 = lastPosition + it->getPos1();
// generate pos1 and pos2
vec2 pos1 = lastPosStore + (tmp1 - lastPosStore)*0.666666666f;
vec2 pos2 = pos + (tmp1 - pos)*0.666666666f;
interpolateCubicBezier(tmpListPoint,
_recurtionMax,
_threshold,
lastPosStore,
pos1,
pos2,
pos,
0,
esvg::render::Point::type::join);
lastPosition = pos;
lastAngle = tmp1;
}
break;
case esvg::render::path_bezierSmoothCurveTo:
// If no previous point, we need to create the last point has start ...
if (tmpListPoint.size() == 0) {
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::join));
}
{
vec2 lastPosStore(lastPosition);
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
vec2 pos = lastPosition + it->getPos();
vec2 tmp1 = lastPosStore*2.0f - lastAngle;
// generate pos1 and pos2
vec2 pos1 = lastPosStore + (tmp1 - lastPosStore)*0.666666666f;
vec2 pos2 = pos + (tmp1 - pos)*0.66666666f;
interpolateCubicBezier(tmpListPoint,
_recurtionMax,
_threshold,
lastPosStore,
pos1,
pos2,
pos,
0,
esvg::render::Point::type::join);
lastPosition = pos;
lastAngle = tmp1;
}
break;
case esvg::render::path_elliptic:
// If no previous point, we need to create the last point has start ...
if (tmpListPoint.size() == 0) {
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::join));
}
{
ememory::SharedPtr<esvg::render::ElementElliptic> tmpIt(ememory::dynamicPointerCast<esvg::render::ElementElliptic>(it));
float angle = tmpIt->m_angle * (M_PI / 180.0);
ESVG_TODO(spacingDist(_level+1) << " Elliptic arc: radius=" << tmpIt->getPos1());
ESVG_TODO(spacingDist(_level+1) << " angle=" << tmpIt->m_angle);
ESVG_TODO(spacingDist(_level+1) << " m_largeArcFlag=" << tmpIt->m_largeArcFlag);
ESVG_TODO(spacingDist(_level+1) << " m_sweepFlag=" << tmpIt->m_sweepFlag);
vec2 lastPosStore(lastPosition);
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
vec2 pos = lastPosition + it->getPos();
float rotationX = tmpIt->m_angle * (M_PI / 180.0);
vec2 radius = tmpIt->getPos1();
#ifdef DEBUG
m_debugInformation.addSegment(lastPosStore, pos);
#endif
vec2 delta = lastPosStore - pos;
float ddd = delta.length();
if ( ddd < 1e-6f
|| radius.x() < 1e-6f
|| radius.y() < 1e-6f) {
ESVG_WARNING("Degenerate arc in Line");
if (tmpListPoint.size() == 0) {
tmpListPoint.push_back(esvg::render::Point(lastPosition, esvg::render::Point::type::join));
}
tmpListPoint.push_back(esvg::render::Point(pos, esvg::render::Point::type::join));
} else {
// Convert to center point parameterization.
// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
// procedure describe here : http://www.w3.org/TR/SVG11/implnote.html#ArcConversionCenterToEndpoint
// Compute delta'
mat2 matrixRotationCenter = etk::mat2Rotate(-rotationX);
vec2 deltaPrim = matrixRotationCenter * (delta*0.5f);
ddd = (deltaPrim.x()*deltaPrim.x())/(radius.x()*radius.x())
+ (deltaPrim.y()*deltaPrim.y())/(radius.y()*radius.y());
if (ddd > 1.0f) {
#ifndef __STDCPP_LLVM__
ddd = std::sqrt(ddd);
#else
ddd = sqrtf(ddd);
#endif
radius *= ddd;
}
// Compute center'
float sss = 0.0f;
float ssa = radius.x()*radius.x()*radius.y()*radius.y()
- radius.x()*radius.x()*deltaPrim.y()*deltaPrim.y()
- radius.y()*radius.y()*deltaPrim.x()*deltaPrim.x();
float ssb = radius.x()*radius.x()*deltaPrim.y()*deltaPrim.y()
+ radius.y()*radius.y()*deltaPrim.x()*deltaPrim.x();
if (ssa < 0.0f) {
ssa = 0.0f;
}
if (ssb > 0.0f) {
#ifndef __STDCPP_LLVM__
sss = std::sqrt(ssa / ssb);
#else
sss = sqrtf(ssa / ssb);
#endif
}
if (tmpIt->m_largeArcFlag == tmpIt->m_sweepFlag) {
sss *= -1.0f;
}
vec2 centerPrime(sss * radius.x() * deltaPrim.y() / radius.y(),
sss * -radius.y() * deltaPrim.x() / radius.x());
// Compute center from center'
mat2 matrix = etk::mat2Rotate(rotationX);
vec2 center = (lastPosStore + pos)*0.5f + matrix*centerPrime;
#ifdef DEBUG
m_debugInformation.addSegment(center-vec2(3.0,3.0), center+vec2(3.0,3.0));
m_debugInformation.addSegment(center-vec2(3.0,-3.0), center+vec2(3.0,-3.0));
#endif
// Calculate theta1, and delta theta.
vec2 vectorA = (deltaPrim - centerPrime) / radius;
vec2 vectorB = (deltaPrim + centerPrime) / radius * -1.0f;
#ifdef DEBUG
m_debugInformation.addSegment(center, center+vectorA*radius.x());
m_debugInformation.addSegment(center, center+vectorB*radius.y());
#endif
// Initial angle
float theta1 = vectorAngle(vec2(1.0f,0.0f), vectorA);
// Delta angle
float deltaTheta = vectorAngle(vectorA, vectorB);
// special case of invert angle...
if ( ( deltaTheta == float(M_PI)
|| deltaTheta == -float(M_PI))
&& tmpIt->m_sweepFlag == false) {
deltaTheta *= -1.0f;
}
if (tmpIt->m_largeArcFlag == true) {
// Choose large arc
if (deltaTheta > 0.0f) {
deltaTheta -= 2.0f*M_PI;
} else {
deltaTheta += 2.0f*M_PI;
}
}
// Approximate the arc using cubic spline segments.
matrix.translate(center);
// Split arc into max 90 degree segments.
// The loop assumes an iteration per end point (including start and end), this +1.
#ifndef __STDCPP_LLVM__
int32_t ndivs = int32_t(std::abs(deltaTheta) / (M_PI*0.5f)) + 1;
#else
int32_t ndivs = int32_t(fabs(deltaTheta) / (M_PI*0.5f)) + 1;
#endif
float hda = (deltaTheta / float(ndivs)) * 0.5f;
#ifndef __STDCPP_LLVM__
float kappa = std::abs(4.0f / 3.0f * (1.0f - std::cos(hda)) / std::sin(hda));
#else
float kappa = fabs(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda));
#endif
if (deltaTheta < 0.0f) {
kappa = -kappa;
}
vec2 pointPosPrevious(0.0,0.0);
vec2 tangentPrevious(0.0,0.0);
for (int32_t iii=0; iii<=ndivs; ++iii) {
float a = theta1 + deltaTheta * (float(iii)/(float)ndivs);
#ifndef __STDCPP_LLVM__
delta = vec2(std::cos(a), std::sin(a));
#else
delta = vec2(cosf(a), sinf(a));
#endif
// position
vec2 pointPos = matrix * vec2(delta.x()*radius.x(), delta.y()*radius.y());
// tangent
vec2 tangent = matrix.applyScaleRotation(vec2(-delta.y()*radius.x() * kappa, delta.x()*radius.y() * kappa));
if (iii > 0) {
vec2 zlastPosStore(lastPosition);
if (it->getRelative() == false) {
lastPosition = vec2(0.0f, 0.0f);
}
vec2 zpos1 = pointPosPrevious + tangentPrevious;
vec2 zpos2 = pointPos - tangent;
vec2 zpos = pointPos;
interpolateCubicBezier(tmpListPoint,
_recurtionMax,
_threshold,
zlastPosStore,
zpos1,
zpos2,
zpos,
0,
esvg::render::Point::type::join);
lastPosition = zpos;
lastAngle = zpos2;
}
pointPosPrevious = pointPos;
tangentPrevious = tangent;
}
}
lastPosition = pos;
}
break;
default:
ESVG_ERROR(spacingDist(_level+1) << " Unknow PATH commant (internal error)");
break;
}
}
// special case : No request end of path ==> open path:
if (tmpListPoint.size() != 0) {
ESVG_VERBOSE("Auto-end PATH");
tmpListPoint.back().setEndPath();
out.addList(tmpListPoint);
tmpListPoint.clear();
}
ESVG_VERBOSE(spacingDist(_level) << " ==> " << out.m_data.size());
for (auto &it : out.m_data) {
ESVG_VERBOSE(spacingDist(_level) << " " << it.size());
}
out.display();
return out;
}