dashing/dashing.hh

285 lines
8.5 KiB
C++

/*
Copyright (c) 2015 Jeff Epler
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 acknowledgement 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.
*/
#pragma once
#include <algorithm>
#include <cmath>
#include <cassert>
#include <vector>
#include <string>
#include <fstream>
#include <stdexcept>
#include <span>
#if defined(DASHING_OMP)
#include <omp.h>
#endif
#include "dashing_F.hh"
namespace dashing
{
struct PSMatrix {
F a, b, c, d, e, f;
PSMatrix inverse() const;
F determinant() const { return a * d - b * c; }
};
PSMatrix Translation(F x, F y);
PSMatrix Rotation(F theta);
PSMatrix XSkew(F xk);
PSMatrix YScale(F ys);
struct Point { F x, y; };
inline Point operator*(const Point &p, const PSMatrix &m) {
return Point{ p.x*m.a + p.y*m.c + m.e,
p.x*m.b + p.y*m.d + m.f };
}
inline Point operator*(const Point &p, F d)
{
return Point{ p.x * d, p.y * d };
}
inline Point operator*(F d, const Point &p)
{
return Point{ p.x * d, p.y * d };
}
inline Point operator+(const Point &p, const Point &q)
{
return Point{ p.x + q.x, p.y * q.y };
}
PSMatrix operator*(const PSMatrix &m1, const PSMatrix m2);
struct Dash {
PSMatrix tr, tf;
std::vector<F> dash, sum;
Dash(const PSMatrix &tr, const std::vector<F> &dash) :
tr{tr}, tf{tr.inverse()}, dash{dash} {}
Dash(F th, F x0, F y0, F dx, F dy,
const std::vector<F>::const_iterator dbegin,
const std::vector<F>::const_iterator dend);
static Dash FromString(const std::string &line, F scale);
};
struct Segment { Point p, q; bool swapped; };
struct Intersection { F u; bool positive; };
inline bool operator<(const Intersection &a, const Intersection &b)
{
return a.u < b.u;
}
// "sort" a segment so that its first component has the lower y-value
inline void ysort(Segment &s) {
if(s.p.y < s.q.y) return;
s.swapped = ! s.swapped;
std::swap(s.p, s.q);
}
inline int intceil(F x) { return int(ceil(x)); }
inline int intfloor(F x) { return int(floor(x)); }
inline F pythonmod(F vx, F wx) {
auto mod = fmod(vx, wx);
if (mod) {
if ((wx < 0) != (mod < 0)) {
mod += wx;
}
} else {
mod = copysign(0.0, wx);
}
return mod;
}
inline size_t utoidx(const Dash &d, F u, F &o) {
u = pythonmod(u, d.sum.back());
for(size_t i = 1; i != d.sum.size(); i++) {
if(u < d.sum[i]) { o = u - d.sum[i-1]; return i-1; }
}
abort(); // should be unreachable
}
template<class Cb>
void uvdraw(const Dash &pattern, F v, F u1, F u2, Cb cb) {
if(pattern.dash.empty()) { cb(v, u1, u2); return; }
F o;
auto i = utoidx(pattern, u1, o);
const auto pi = pattern.dash[i];
if(i % 2 == 0) {
// Pattern starts inside a dash. Output the dash then skip the gap
cb(v, u1, std::min(u2, u1+pi-o));
u1 += pi-o;
u1 += pattern.dash[i+1];
i += 2;
} else {
// Pattern starts inside a gap. skip the gap
u1 += pi-o;
i += 1;
}
for(auto u = u1; u < u2;) {
if(i >= pattern.dash.size()) i = 0;
const auto pi = pattern.dash[i];
cb(v, u, std::min(u2, u+pi));
u += pi;
u += pattern.dash[i+1];
i+=2;
}
}
template<class Cb, class Wr>
void uvspans(const Dash &pattern, std::vector<Segment> & segments, Cb cb, std::vector<Intersection> &uu, Wr wr) {
if(segments.empty()) return; // no segments
for(auto &s : segments) ysort(s);
std::sort(segments.begin(), segments.end(),
[](const Segment &a, const Segment &b) {
return a.p.y < b.p.y; // sort in increasing p.y
});
// we want to maintain the heap condition in such a way that we can always
// quickly pop items that our span has moved past.
// C++ heaps are max-heaps, so we need a decreasing sort.
auto heapcmp = [](const Segment &a, const Segment &b) {
return b.q.y < a.q.y; // sort in decreasing q.y;
};
auto segments_begin = segments.begin();
auto heap_begin = segments.begin(), heap_end = segments.begin();
auto vstart = intfloor(segments.front().p.y);
auto vend = intceil(std::max_element(segments.begin(), segments.end(),
[](const Segment &a, const Segment &b) {
return a.q.y < b.q.y; // sort in increasing q.y;
})->q.y);
// sweep-line algorithm to intersects spans with segments
// "active" holds segments that may intersect with this span;
// when v moves below an active segment, drop it from the active heap.
// when v moves into a remaining segment, move it from segments to active.
for(auto v = vstart; v != vend; v++) {
uu.clear();
while(heap_begin != heap_end && heap_begin->q.y < v)
{
std::pop_heap(heap_begin, heap_end, heapcmp);
heap_end --;
}
while(segments_begin != segments.end() && segments_begin->p.y < v) {
const auto &s = *segments_begin;
if(s.q.y >= v) {
*heap_end++ = s;
std::push_heap(heap_begin, heap_end, heapcmp);
}
segments_begin ++;
}
for(const auto &s : std::span(heap_begin, heap_end)) {
auto du = s.q.x - s.p.x;
auto dv = s.q.y - s.p.y;
assert(dv);
uu.push_back(
Intersection{s.p.x + du * (v - s.p.y) / dv,s.swapped});
}
std::sort(uu.begin(), uu.end());
int winding = 0;
F old_u = -std::numeric_limits<F>::infinity();
for(const auto &isect : uu) {
if(wr(winding)) uvdraw(pattern, v, old_u, isect.u, cb);
winding += 2*isect.positive - 1;
old_u = isect.u;
}
}
}
struct HatchPattern {
std::vector<Dash> d;
static HatchPattern FromFile(std::istream &fi, F scale) {
HatchPattern result;
std::string line;
while(getline(fi, line)) {
auto i = line.find(";");
if(i != line.npos) line.erase(i, line.npos);
while(!line.empty() && isspace(line.back())) {
line.pop_back();
}
if(line.empty()) continue;
if(line[0] == '*') continue;
result.d.push_back(Dash::FromString(line, scale));
}
return result;
}
static HatchPattern FromFile(const char *filename, F scale) {
std::ifstream fi(filename);
return FromFile(fi, scale);
}
};
template<class It, class Cb, class Wr>
void xyhatch(const Dash &pattern, It start, It end, Cb cb, Wr wr) {
std::vector<Segment> uvsegments;
uvsegments.reserve(end-start);
std::vector<Intersection> uu;
uu.reserve(8);
bool swapped = pattern.tf.determinant() < 0;
std::transform(start, end, std::back_inserter(uvsegments),
[&](const Segment &s)
{ return Segment{s.p * pattern.tf, s.q * pattern.tf, swapped != s.swapped };
});
uvspans(pattern, uvsegments, [&](F v, F u1, F u2) {
Point p{u1, v}, q{u2, v};
cb(Segment{ p * pattern.tr, q * pattern.tr, false });
}, uu, wr);
}
template<class It, class Cb, class Wr>
void xyhatch(const HatchPattern &pattern, It start, It end, Cb cb, Wr wr) {
for(const auto &i : pattern.d) xyhatch(i, start, end, cb, wr);
}
template<class C, class Cb, class Wr>
void xyhatch(const HatchPattern &pattern, const C &c, Cb cb, Wr wr) {
xyhatch(pattern, c.begin(), c.end(), cb, wr);
}
#if defined(DASHING_OMP)
template<class It, class Cb, class Wr>
void xyhatch_omp(const HatchPattern &pattern, It start, It end, Cb cb, Wr wr) {
#pragma omp parallel for
for(const auto &i : pattern.d) {
int iam = omp_get_thread_num();
xyhatch(i, start, end, [iam, &cb](Segment s) { cb(s, iam); }, wr);
}
}
template<class C, class Cb, class Wr>
void xyhatch_omp(const HatchPattern &pattern, const C &c, Cb cb, Wr wr) {
xyhatch_omp(pattern, c.begin(), c.end(), cb, wr);
}
#endif
}