/* * Copyright (c) 2015 Tricoire Sebastien 3dsman@free.fr * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * */ #include "OE_curve.h" #include "OE_utils.h" #include #include #include #include #include #include #include unsigned char OE_curve::lineColor[] = {0,160,192,255}; unsigned char OE_curve::controlLineColor[] = {100,60,92,255}; unsigned char OE_curve::controlPointColor[] = {100,60,92,255}; unsigned char OE_curve::controlEndPointColor[] = {100,60,92,255}; OE_curve::OE_curve() { } OE_curve::OE_curve(std::vector points, bool closed) { pts = points; this->closed = closed; // Flag indicating if shapes should be treated as closed. } OE_curve::~OE_curve() { } float OE_curve::getLength(float maxDist) { if (check()) { double len = 0; vector_2d vect; std::vector tmpdisk = discretizeFast(maxDist); for (unsigned j = 1; j < tmpdisk.size(); j++) { vect = tmpdisk.at(j)-tmpdisk.at(j-1); len += vect.len(); } return len; } return -1; } bool OE_curve::getPoint(uint16_t nb, float* x, float* y) { if ((pts.size())&&(nb<=pts.size())) { *x = pts.at(nb-1).x; *y = pts.at(nb-1).y; return true; } return false; } bool OE_curve::getPoint(uint16_t nb, vector_2d* pt) { if ((pts.size())&&(nb<=pts.size())) { pt->x = pts.at(nb-1).x; pt->y = pts.at(nb-1).y; return true; } return false; } bool OE_curve::setPoint(uint16_t nb, float x, float y) { if (nb<=pts.size()) { pts[(nb-1)] = vector_2d(x,y); needRefresh = true; return true; } return false; } bool OE_curve::addPoint( float x, float y) { pts.push_back(vector_2d(x,y)); needRefresh = true; return true; } bool OE_curve::addPoint( vector_2d pt) { pts.push_back(pt); needRefresh = true; return true; } void OE_curve::lineTo(float x, float y) { float px,py, dx,dy; if (pts.size() > 0) { getPoint(pts.size(), &px, &py); dx = x - px; dy = y - py; addPoint(px + dx/3.0f, py + dy/3.0f); addPoint(x - dx/3.0f, y - dy/3.0f); addPoint(x, y); } } void OE_curve::lineTo(vector_2d pt) { vector_2d p,d; if (pts.size() > 0) { getPoint(pts.size(), &p); d = pt-p; addPoint(p + d/3.0f); addPoint(pt - d/3.0f); addPoint(pt); } } void OE_curve::cubicBezTo(float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) { addPoint(cpx1, cpy1); addPoint(cpx2, cpy2); addPoint(x, y); } #define EPSILON (1e-12) bool OE_curve::ptInBounds( vector_2d pt, float* bounds) { return pt.x >= bounds[0] && pt.x <= bounds[2] && pt.y >= bounds[1] && pt.y <= bounds[3]; } double OE_curve::evalBezier(double t, double p0, double p1, double p2, double p3) { double it = 1.0-t; return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; } std::vector OE_curve::interPoint(vector_2d pt1, vector_2d pt2, vector_2d pt3, vector_2d pt4, float t) { std::vector out; vector_2d pt12 = (pt2-pt1)*t+pt1; vector_2d pt23 = (pt3-pt2)*t+pt2; vector_2d pt34 = (pt4-pt3)*t+pt3; vector_2d pt123 = (pt23-pt12)*t+pt12; vector_2d pt234 = (pt34-pt23)*t+pt23; vector_2d pt1234 = (pt234-pt123)*t+pt123; out.push_back(pt123); out.push_back(pt1234); out.push_back(pt234); return out; } void OE_curve::segmentBounds(float* bounds, vector_2d* segment) { int i, j, count; double roots[2], a, b, c, b2ac, t, v; vector_2d* v0 = &segment[0]; vector_2d* v1 = &segment[1]; vector_2d* v2 = &segment[2]; vector_2d* v3 = &segment[3]; // Start the bounding box by end points bounds[0] = minf(v0->x, v3->x); bounds[1] = minf(v0->y, v3->y); bounds[2] = maxf(v0->x, v3->x); bounds[3] = maxf(v0->y, v3->y); // Bezier segment fits inside the convex hull of it's control points. // If control points are inside the bounds, we're done. if (ptInBounds(*v1, bounds) && ptInBounds(*v2, bounds)) return; // Add bezier segment inflection points in X and Y. for (i = 0; i < 2; i++) { if (i){ a = -3.0 * v0->y+ 9.0 * v1->y - 9.0 * v2->y + 3.0 * v3->y; b = 6.0 * v0->y - 12.0 * v1->y + 6.0 * v2->y; c = 3.0 * v1->y - 3.0 * v0->y; } else{ a = -3.0 * v0->x + 9.0 * v1->x - 9.0 * v2->x + 3.0 * v3->x; b = 6.0 * v0->x - 12.0 * v1->x + 6.0 * v2->x; c = 3.0 * v1->x - 3.0 * v0->x; } count = 0; if (fabs(a) < EPSILON) { if (fabs(b) > EPSILON) { t = -c / b; if (t > EPSILON && t < 1.0-EPSILON) roots[count++] = t; } } else { b2ac = b*b - 4.0*c*a; if (b2ac > EPSILON) { t = (-b + sqrt(b2ac)) / (2.0 * a); if (t > EPSILON && t < 1.0-EPSILON) roots[count++] = t; t = (-b - sqrt(b2ac)) / (2.0 * a); if (t > EPSILON && t < 1.0-EPSILON) roots[count++] = t; } } for (j = 0; j < count; j++) { if (i){ v = evalBezier(roots[j], v0->y, v1->y, v2->y, v3->y); }else{ v = evalBezier(roots[j], v0->x, v1->x, v2->x, v3->x); } bounds[0+i] = minf(bounds[0+i], (float)v); bounds[2+i] = maxf(bounds[2+i], (float)v); } } } void OE_curve::getBound(float* xMin, float* yMin, float* xMax, float* yMax) { float tmpbounds[4]; vector_2d* curve; // Find bounds for (unsigned i = 0; i < pts.size()-1; i += 3) { curve = &pts[i]; segmentBounds(tmpbounds, curve); if (i == 0) { bounds[0] = tmpbounds[0]; bounds[1] = tmpbounds[1]; bounds[2] = tmpbounds[2]; bounds[3] = tmpbounds[3]; } else { bounds[0] = minf(bounds[0], tmpbounds[0]); bounds[1] = minf(bounds[1], tmpbounds[1]); bounds[2] = maxf(bounds[2], tmpbounds[2]); bounds[3] = maxf(bounds[3], tmpbounds[3]); } } *xMin = bounds[0]; *yMin = bounds[1]; *xMax = bounds[2]; *yMax = bounds[3]; } std::vector OE_curve::subCurve( float start, float end, bool rev) { std::vector out; if (start>end) { if(start==(pts.size()-1)/3) start = 0; else if(end==0) end = (pts.size()-1)/3; } if (((start>end)&&(!closed))||(start==end)||(start<0)||(end<0)||(start>(pts.size()-1.0)/3.0)||(end>(pts.size()-1.0)/3.0)) return out; std::vector tangeant; bool loop =false; if (start>end) loop = true; float scaleVect = 1; float nearPt; nearPt = floor(start); vector_2d* ps = &pts[nearPt*3]; vector_2d* ps2; tangeant = interPoint(ps[0],ps[1],ps[2],ps[3], start-nearPt); out.push_back(tangeant[1]); out.push_back(tangeant[2]); scaleVect = 1-(start-nearPt); //middle of the curve while ((nearPt+1next = next; return true; } OE_curve* OE_curve::getNext() { return next; } */ int OE_curve::getNpts(){return pts.size();} bool OE_curve::getClosed(){return closed;} void OE_curve::setClosed(bool closed){this->closed = closed; needRefresh = true;} bool OE_curve::check(){return ((pts.size()>0)&&((pts.size())%3==1)) ;} bool OE_curve::refresh(float dpi) { if (dpi ==0)return false; discPts = discretizeFast(dpi); return true; } float OE_curve::distPtSeg(vector_2d pt, vector_2d seg1, vector_2d seg2) { float d2, t; vector_2d pq, d; pq = seg2-seg1; d = pt-seg1; d2 = pq.x*pq.x + pq.y*pq.y; t = pq.x*d.x + pq.y*d.y; if (d2 > 0) t /= d2; if (t < 0) t = 0; else if (t > 1) t = 1; d = seg1 + t*pq - pt; return d.x*d.x + d.y*d.y; } std::vector OE_curve::discretizeCubicBez(vector_2d pt1, vector_2d pt2, vector_2d pt3, vector_2d pt4, float tol, int level) { std::vector out; float d; if (level > 12) return out; vector_2d pt12 = (pt1+pt2)*0.5f; vector_2d pt23 = (pt2+pt3)*0.5f; vector_2d pt34 = (pt3+pt4)*0.5f; vector_2d pt123 = (pt12+pt23)*0.5f; vector_2d pt234 = (pt23+pt34)*0.5f; vector_2d pt1234 = (pt123+pt234)*0.5f; d = distPtSeg(pt1234, pt1, pt4); if (d > tol*tol) { out = discretizeCubicBez(pt1, pt12, pt123, pt1234, tol, level+1); std::vector tmpdisc = discretizeCubicBez(pt1234, pt234, pt34, pt4, tol, level+1); out.insert(out.end(), tmpdisc.begin(), tmpdisc.end()); } else { out.push_back(pt4); } return out; } std::vector OE_curve::discretizeFast(float maxDist) { std::vector out; if (check()) { out.push_back(pts.at(0)); for (unsigned i = 0; i < pts.size()-3; i += 3) { std::vector tmpdisk = discretizeCubicBez(pts.at(i), pts.at(i+1), pts.at(i+2), pts.at(i+3), maxDist, 0); out.insert(out.end(), tmpdisk.begin(), tmpdisk.end()); } } return out; } std::vector OE_curve::discretizeRegular(float dist) { std::vector out; if (check()) { float vectlen, tmplen = 0; vector_2d vect; std::vector disc, tmpdisc ; tmpdisc.push_back(pts[0]); for (unsigned i = 0; i < pts.size()-3; i += 3) { std::vector tmpdisk = discretizeCubicBez(pts[i], pts[i+1], pts[i+2], pts[i+3], dist/100.0, 0); tmpdisc.insert(tmpdisc.end(), tmpdisk.begin(), tmpdisk.end()); } if (closed) { tmpdisc.push_back(pts[0]); } tmplen =0; out.push_back(tmpdisc[0]); for (unsigned i = 0; i < tmpdisc.size()-1; i += 1) { vect = tmpdisc[i]-tmpdisc[i+1]; vectlen=vect.len(); tmplen = tmplen + vectlen; while (tmplen >= dist) { tmplen -= dist; out.push_back(vect*(tmplen/vectlen)+tmpdisc.at(i+1)); } } out.push_back(tmpdisc.at(tmpdisc.size()-1)); return out; } return out; }