diff options
Diffstat (limited to 'src/livarot')
35 files changed, 25816 insertions, 0 deletions
diff --git a/src/livarot/AVL.cpp b/src/livarot/AVL.cpp new file mode 100644 index 0000000..9bf6eb4 --- /dev/null +++ b/src/livarot/AVL.cpp @@ -0,0 +1,969 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "AVL.h" + +/* + * the algorithm explanation for this code comes from purists.org, which seems to have disappeared since + * it's a classic AVL tree rebalancing, nothing fancy + */ + +AVLTree::AVLTree() +{ + MakeNew(); +} + +AVLTree::~AVLTree() +{ + MakeDelete(); +} + +void AVLTree::MakeNew() +{ + for (int i = 0; i < 2; i++) + { + elem[i] = nullptr; + child[i] = nullptr; + } + + parent = nullptr; + balance = 0; +} + +void AVLTree::MakeDelete() +{ + for (int i = 0; i < 2; i++) { + if (elem[i]) { + elem[i]->elem[1 - i] = elem[1 - i]; + } + elem[i] = nullptr; + } +} + +AVLTree *AVLTree::Leftmost() +{ + return leafFromParent(nullptr, LEFT); +} + +AVLTree *AVLTree::leaf(AVLTree *from, Side s) +{ + if (from == child[1 - s]) { + if (child[s]) { + return child[s]->leafFromParent(this, s); + } + else if (parent) { + return parent->leaf(this, s); + } + } + else if (from == child[s]) { + if (parent) { + return parent->leaf(this, s); + } + } + + return nullptr; +} + +AVLTree *AVLTree::leafFromParent(AVLTree */*from*/, Side s) +{ + if (child[s]) { + return child[s]->leafFromParent(this, s); + } + + return this; +} + +int +AVLTree::RestoreBalances (AVLTree * from, AVLTree * &racine) +{ + if (from == nullptr) + { + if (parent) + return parent->RestoreBalances (this, racine); + } + else + { + if (balance == 0) + { + if (from == child[LEFT]) + balance = 1; + if (from == child[RIGHT]) + balance = -1; + if (parent) + return parent->RestoreBalances (this, racine); + return avl_no_err; + } + else if (balance > 0) + { + if (from == child[RIGHT]) + { + balance = 0; + return avl_no_err; + } + if (child[LEFT] == nullptr) + { +// cout << "mierda\n"; + return avl_bal_err; + } + AVLTree *a = this; + AVLTree *b = child[LEFT]; + AVLTree *e = child[RIGHT]; + AVLTree *c = child[LEFT]->child[LEFT]; + AVLTree *d = child[LEFT]->child[RIGHT]; + if (child[LEFT]->balance > 0) + { + AVLTree *r = parent; + + a->parent = b; + b->child[RIGHT] = a; + a->child[RIGHT] = e; + if (e) + e->parent = a; + a->child[LEFT] = d; + if (d) + d->parent = a; + b->child[LEFT] = c; + if (c) + c->parent = b; + b->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = b; + if (r->child[RIGHT] == a) + r->child[RIGHT] = b; + } + if (racine == a) + racine = b; + + a->balance = 0; + b->balance = 0; + return avl_no_err; + } + else + { + if (child[LEFT]->child[RIGHT] == nullptr) + { + // cout << "mierda\n"; + return avl_bal_err; + } + AVLTree *f = child[LEFT]->child[RIGHT]->child[LEFT]; + AVLTree *g = child[LEFT]->child[RIGHT]->child[RIGHT]; + AVLTree *r = parent; + + a->parent = d; + d->child[RIGHT] = a; + b->parent = d; + d->child[LEFT] = b; + a->child[LEFT] = g; + if (g) + g->parent = a; + a->child[RIGHT] = e; + if (e) + e->parent = a; + b->child[LEFT] = c; + if (c) + c->parent = b; + b->child[RIGHT] = f; + if (f) + f->parent = b; + + d->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = d; + if (r->child[RIGHT] == a) + r->child[RIGHT] = d; + } + if (racine == a) + racine = d; + + int old_bal = d->balance; + d->balance = 0; + if (old_bal == 0) + { + a->balance = 0; + b->balance = 0; + } + else if (old_bal > 0) + { + a->balance = -1; + b->balance = 0; + } + else if (old_bal < 0) + { + a->balance = 0; + b->balance = 1; + } + return avl_no_err; + } + } + else if (balance < 0) + { + if (from == child[LEFT]) + { + balance = 0; + return avl_no_err; + } + if (child[RIGHT] == nullptr) + { +// cout << "mierda\n"; + return avl_bal_err; + } + AVLTree *a = this; + AVLTree *b = child[RIGHT]; + AVLTree *e = child[LEFT]; + AVLTree *c = child[RIGHT]->child[RIGHT]; + AVLTree *d = child[RIGHT]->child[LEFT]; + AVLTree *r = parent; + if (child[RIGHT]->balance < 0) + { + + a->parent = b; + b->child[LEFT] = a; + a->child[LEFT] = e; + if (e) + e->parent = a; + a->child[RIGHT] = d; + if (d) + d->parent = a; + b->child[RIGHT] = c; + if (c) + c->parent = b; + b->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = b; + if (r->child[RIGHT] == a) + r->child[RIGHT] = b; + } + if (racine == a) + racine = b; + a->balance = 0; + b->balance = 0; + return avl_no_err; + } + else + { + if (child[RIGHT]->child[LEFT] == nullptr) + { +// cout << "mierda\n"; + return avl_bal_err; + } + AVLTree *f = child[RIGHT]->child[LEFT]->child[RIGHT]; + AVLTree *g = child[RIGHT]->child[LEFT]->child[LEFT]; + + a->parent = d; + d->child[LEFT] = a; + b->parent = d; + d->child[RIGHT] = b; + a->child[RIGHT] = g; + if (g) + g->parent = a; + a->child[LEFT] = e; + if (e) + e->parent = a; + b->child[RIGHT] = c; + if (c) + c->parent = b; + b->child[LEFT] = f; + if (f) + f->parent = b; + + d->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = d; + if (r->child[RIGHT] == a) + r->child[RIGHT] = d; + } + if (racine == a) + racine = d; + int old_bal = d->balance; + d->balance = 0; + if (old_bal == 0) + { + a->balance = 0; + b->balance = 0; + } + else if (old_bal > 0) + { + a->balance = 0; + b->balance = -1; + } + else if (old_bal < 0) + { + a->balance = 1; + b->balance = 0; + } + return avl_no_err; + } + } + } + return avl_no_err; +} + +int +AVLTree::RestoreBalances (int diff, AVLTree * &racine) +{ + if (balance > 0) + { + if (diff < 0) + { + balance = 0; + if (parent) + { + if (this == parent->child[RIGHT]) + return parent->RestoreBalances (1, racine); + if (this == parent->child[LEFT]) + return parent->RestoreBalances (-1, racine); + } + return avl_no_err; + } + else if (diff == 0) + { + } + else if (diff > 0) + { + if (child[LEFT] == nullptr) + { +// cout << "un probleme\n"; + return avl_bal_err; + } + AVLTree *r = parent; + AVLTree *a = this; + AVLTree *b = child[RIGHT]; + AVLTree *e = child[LEFT]; + AVLTree *f = e->child[RIGHT]; + AVLTree *g = e->child[LEFT]; + if (e->balance > 0) + { + e->child[RIGHT] = a; + e->child[LEFT] = g; + a->child[RIGHT] = b; + a->child[LEFT] = f; + if (a) + a->parent = e; + if (g) + g->parent = e; + if (b) + b->parent = a; + if (f) + f->parent = a; + e->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = e; + if (r->child[RIGHT] == a) + r->child[RIGHT] = e; + } + if (racine == this) + racine = e; + e->balance = 0; + a->balance = 0; + if (r) + { + if (e == r->child[RIGHT]) + return r->RestoreBalances (1, racine); + if (e == r->child[LEFT]) + return r->RestoreBalances (-1, racine); + } + return avl_no_err; + } + else if (e->balance == 0) + { + e->child[RIGHT] = a; + e->child[LEFT] = g; + a->child[RIGHT] = b; + a->child[LEFT] = f; + if (a) + a->parent = e; + if (g) + g->parent = e; + if (b) + b->parent = a; + if (f) + f->parent = a; + e->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = e; + if (r->child[RIGHT] == a) + r->child[RIGHT] = e; + } + if (racine == this) + racine = e; + e->balance = -1; + a->balance = 1; + return avl_no_err; + } + else if (e->balance < 0) + { + if (child[LEFT]->child[RIGHT] == nullptr) + { +// cout << "un probleme\n"; + return avl_bal_err; + } + AVLTree *i = child[LEFT]->child[RIGHT]->child[RIGHT]; + AVLTree *j = child[LEFT]->child[RIGHT]->child[LEFT]; + + f->child[RIGHT] = a; + f->child[LEFT] = e; + a->child[RIGHT] = b; + a->child[LEFT] = i; + e->child[RIGHT] = j; + e->child[LEFT] = g; + if (b) + b->parent = a; + if (i) + i->parent = a; + if (g) + g->parent = e; + if (j) + j->parent = e; + if (a) + a->parent = f; + if (e) + e->parent = f; + f->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = f; + if (r->child[RIGHT] == a) + r->child[RIGHT] = f; + } + if (racine == this) + racine = f; + int oBal = f->balance; + f->balance = 0; + if (oBal > 0) + { + a->balance = -1; + e->balance = 0; + } + else if (oBal == 0) + { + a->balance = 0; + e->balance = 0; + } + else if (oBal < 0) + { + a->balance = 0; + e->balance = 1; + } + if (r) + { + if (f == r->child[RIGHT]) + return r->RestoreBalances (1, racine); + if (f == r->child[LEFT]) + return r->RestoreBalances (-1, racine); + } + return avl_no_err; + } + } + } + else if (balance == 0) + { + if (diff < 0) + { + balance = -1; + } + else if (diff == 0) + { + } + else if (diff > 0) + { + balance = 1; + } + return avl_no_err; + } + else if (balance < 0) + { + if (diff < 0) + { + if (child[RIGHT] == nullptr) + { +// cout << "un probleme\n"; + return avl_bal_err; + } + AVLTree *r = parent; + AVLTree *a = this; + AVLTree *b = child[LEFT]; + AVLTree *e = child[RIGHT]; + AVLTree *f = e->child[LEFT]; + AVLTree *g = e->child[RIGHT]; + if (e->balance < 0) + { + e->child[LEFT] = a; + e->child[RIGHT] = g; + a->child[LEFT] = b; + a->child[RIGHT] = f; + if (a) + a->parent = e; + if (g) + g->parent = e; + if (b) + b->parent = a; + if (f) + f->parent = a; + e->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = e; + if (r->child[RIGHT] == a) + r->child[RIGHT] = e; + } + if (racine == this) + racine = e; + e->balance = 0; + a->balance = 0; + if (r) + { + if (e == r->child[RIGHT]) + return r->RestoreBalances (1, racine); + if (e == r->child[LEFT]) + return r->RestoreBalances (-1, racine); + } + return avl_no_err; + } + else if (e->balance == 0) + { + e->child[LEFT] = a; + e->child[RIGHT] = g; + a->child[LEFT] = b; + a->child[RIGHT] = f; + if (a) + a->parent = e; + if (g) + g->parent = e; + if (b) + b->parent = a; + if (f) + f->parent = a; + e->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = e; + if (r->child[RIGHT] == a) + r->child[RIGHT] = e; + } + if (racine == this) + racine = e; + e->balance = 1; + a->balance = -1; + return avl_no_err; + } + else if (e->balance > 0) + { + if (child[RIGHT]->child[LEFT] == nullptr) + { +// cout << "un probleme\n"; + return avl_bal_err; + } + AVLTree *i = child[RIGHT]->child[LEFT]->child[LEFT]; + AVLTree *j = child[RIGHT]->child[LEFT]->child[RIGHT]; + + f->child[LEFT] = a; + f->child[RIGHT] = e; + a->child[LEFT] = b; + a->child[RIGHT] = i; + e->child[LEFT] = j; + e->child[RIGHT] = g; + if (b) + b->parent = a; + if (i) + i->parent = a; + if (g) + g->parent = e; + if (j) + j->parent = e; + if (a) + a->parent = f; + if (e) + e->parent = f; + f->parent = r; + if (r) + { + if (r->child[LEFT] == a) + r->child[LEFT] = f; + if (r->child[RIGHT] == a) + r->child[RIGHT] = f; + } + if (racine == this) + racine = f; + int oBal = f->balance; + f->balance = 0; + if (oBal > 0) + { + a->balance = 0; + e->balance = -1; + } + else if (oBal == 0) + { + a->balance = 0; + e->balance = 0; + } + else if (oBal < 0) + { + a->balance = 1; + e->balance = 0; + } + if (r) + { + if (f == r->child[RIGHT]) + return r->RestoreBalances (1, racine); + if (f == r->child[LEFT]) + return r->RestoreBalances (-1, racine); + } + return avl_no_err; + } + } + else if (diff == 0) + { + } + else if (diff > 0) + { + balance = 0; + if (parent) + { + if (this == parent->child[RIGHT]) + return parent->RestoreBalances (1, racine); + if (this == parent->child[LEFT]) + return parent->RestoreBalances (-1, racine); + } + return avl_no_err; + } + } + return avl_no_err; +} + +/* + * removal + */ +int +AVLTree::Remove (AVLTree * &racine, bool rebalance) +{ + AVLTree *startNode = nullptr; + int remDiff = 0; + int res = Remove (racine, startNode, remDiff); + if (res == avl_no_err && rebalance && startNode) + res = startNode->RestoreBalances (remDiff, racine); + return res; +} + +int +AVLTree::Remove (AVLTree * &racine, AVLTree * &startNode, int &diff) +{ + if (elem[LEFT]) + elem[LEFT]->elem[RIGHT] = elem[RIGHT]; + if (elem[RIGHT]) + elem[RIGHT]->elem[LEFT] = elem[LEFT]; + elem[LEFT] = elem[RIGHT] = nullptr; + + if (child[LEFT] && child[RIGHT]) + { + AVLTree *newMe = child[LEFT]->leafFromParent(this, RIGHT); + if (newMe == nullptr || newMe->child[RIGHT]) + { +// cout << "pas normal\n"; + return avl_rm_err; + } + if (newMe == child[LEFT]) + { + startNode = newMe; + diff = -1; + newMe->child[RIGHT] = child[RIGHT]; + child[RIGHT]->parent = newMe; + newMe->parent = parent; + if (parent) + { + if (parent->child[LEFT] == this) + parent->child[LEFT] = newMe; + if (parent->child[RIGHT] == this) + parent->child[RIGHT] = newMe; + } + } + else + { + AVLTree *oParent = newMe->parent; + startNode = oParent; + diff = 1; + + oParent->child[RIGHT] = newMe->child[LEFT]; + if (newMe->child[LEFT]) + newMe->child[LEFT]->parent = oParent; + + newMe->parent = parent; + newMe->child[LEFT] = child[LEFT]; + newMe->child[RIGHT] = child[RIGHT]; + if (parent) + { + if (parent->child[LEFT] == this) + parent->child[LEFT] = newMe; + if (parent->child[RIGHT] == this) + parent->child[RIGHT] = newMe; + } + if (child[LEFT]) + child[LEFT]->parent = newMe; + if (child[RIGHT]) + child[RIGHT]->parent = newMe; + } + newMe->balance = balance; + if (racine == this) + racine = newMe; + } + else if (child[LEFT]) + { + startNode = parent; + diff = 0; + if (parent) + { + if (this == parent->child[LEFT]) + diff = -1; + if (this == parent->child[RIGHT]) + diff = 1; + } + if (parent) + { + if (parent->child[LEFT] == this) + parent->child[LEFT] = child[LEFT]; + if (parent->child[RIGHT] == this) + parent->child[RIGHT] = child[LEFT]; + } + if (child[LEFT]->parent == this) + child[LEFT]->parent = parent; + if (racine == this) + racine = child[LEFT]; + } + else if (child[RIGHT]) + { + startNode = parent; + diff = 0; + if (parent) + { + if (this == parent->child[LEFT]) + diff = -1; + if (this == parent->child[RIGHT]) + diff = 1; + } + if (parent) + { + if (parent->child[LEFT] == this) + parent->child[LEFT] = child[RIGHT]; + if (parent->child[RIGHT] == this) + parent->child[RIGHT] = child[RIGHT]; + } + if (child[RIGHT]->parent == this) + child[RIGHT]->parent = parent; + if (racine == this) + racine = child[RIGHT]; + } + else + { + startNode = parent; + diff = 0; + if (parent) + { + if (this == parent->child[LEFT]) + diff = -1; + if (this == parent->child[RIGHT]) + diff = 1; + } + if (parent) + { + if (parent->child[LEFT] == this) + parent->child[LEFT] = nullptr; + if (parent->child[RIGHT] == this) + parent->child[RIGHT] = nullptr; + } + if (racine == this) + racine = nullptr; + } + parent = child[RIGHT] = child[LEFT] = nullptr; + balance = 0; + return avl_no_err; +} + +/* + * insertion + */ +int +AVLTree::Insert (AVLTree * &racine, int insertType, AVLTree * insertL, + AVLTree * insertR, bool rebalance) +{ + int res = Insert (racine, insertType, insertL, insertR); + if (res == avl_no_err && rebalance) + res = RestoreBalances ((AVLTree *) nullptr, racine); + return res; +} + +int +AVLTree::Insert (AVLTree * &racine, int insertType, AVLTree * insertL, + AVLTree * insertR) +{ + if (racine == nullptr) + { + racine = this; + return avl_no_err; + } + else + { + if (insertType == not_found) + { +// cout << "pb avec l'arbre de raster\n"; + return avl_ins_err; + } + else if (insertType == found_on_left) + { + if (insertR == nullptr || insertR->child[LEFT]) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + insertR->child[LEFT] = this; + parent = insertR; + insertOn(LEFT, insertR); + } + else if (insertType == found_on_right) + { + if (insertL == nullptr || insertL->child[RIGHT]) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + insertL->child[RIGHT] = this; + parent = insertL; + insertOn(RIGHT, insertL); + } + else if (insertType == found_between) + { + if (insertR == nullptr || insertL == nullptr + || (insertR->child[LEFT] != nullptr && insertL->child[RIGHT] != nullptr)) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + if (insertR->child[LEFT] == nullptr) + { + insertR->child[LEFT] = this; + parent = insertR; + } + else if (insertL->child[RIGHT] == nullptr) + { + insertL->child[RIGHT] = this; + parent = insertL; + } + insertBetween (insertL, insertR); + } + else if (insertType == found_exact) + { + if (insertL == nullptr) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + // et on insere + + if (insertL->child[RIGHT]) + { + insertL = insertL->child[RIGHT]->leafFromParent(insertL, LEFT); + if (insertL->child[LEFT]) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + insertL->child[LEFT] = this; + this->parent = insertL; + insertBetween (insertL->elem[LEFT], insertL); + } + else + { + insertL->child[RIGHT] = this; + parent = insertL; + insertBetween (insertL, insertL->elem[RIGHT]); + } + } + else + { + // cout << "code incorrect\n"; + return avl_ins_err; + } + } + return avl_no_err; +} + +void +AVLTree::Relocate (AVLTree * to) +{ + if (elem[LEFT]) + elem[LEFT]->elem[RIGHT] = to; + if (elem[RIGHT]) + elem[RIGHT]->elem[LEFT] = to; + to->elem[LEFT] = elem[LEFT]; + to->elem[RIGHT] = elem[RIGHT]; + + if (parent) + { + if (parent->child[LEFT] == this) + parent->child[LEFT] = to; + if (parent->child[RIGHT] == this) + parent->child[RIGHT] = to; + } + if (child[RIGHT]) + { + child[RIGHT]->parent = to; + } + if (child[LEFT]) + { + child[LEFT]->parent = to; + } + to->parent = parent; + to->child[RIGHT] = child[RIGHT]; + to->child[LEFT] = child[LEFT]; +} + + +void AVLTree::insertOn(Side s, AVLTree *of) +{ + elem[1 - s] = of; + if (of) + of->elem[s] = this; +} + +void AVLTree::insertBetween(AVLTree *l, AVLTree *r) +{ + if (l) + l->elem[RIGHT] = this; + if (r) + r->elem[LEFT] = this; + elem[LEFT] = l; + elem[RIGHT] = r; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/AVL.h b/src/livarot/AVL.h new file mode 100644 index 0000000..5e0856c --- /dev/null +++ b/src/livarot/AVL.h @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2014 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* + * AVL.h + * nlivarot + * + * Created by fred on Mon Jun 16 2003. + * + */ + +#ifndef my_avl +#define my_avl + +#include <cstdlib> +#include "LivarotDefs.h" + +/* + * base class providing AVL tree functionnality, that is binary balanced tree + * there is no Find() function because the class only deal with topological info + * subclasses of this class have to implement a Find(), and most certainly to + * override the Insert() function + */ + +class AVLTree +{ +public: + + AVLTree *elem[2]; + + // left most node (ie, with smallest key) in the subtree of this node + AVLTree *Leftmost(); + +protected: + + AVLTree *child[2]; + + AVLTree(); + virtual ~AVLTree(); + + // constructor/destructor meant to be called for an array of AVLTree created by malloc + void MakeNew(); + void MakeDelete(); + + // insertion of the present node in the tree + // insertType is the insertion type (defined in LivarotDefs.h: not_found, found_exact, found_on_left, etc) + // insertL is the node in the tree that is immediatly before the current one, NULL is the present node goes to the + // leftmost position. if insertType == found_exact, insertL should be the node with ak key + // equal to that of the present node + int Insert(AVLTree * &racine, int insertType, AVLTree *insertL, + AVLTree * insertR, bool rebalance); + + // called when this node is relocated to a new position in memory, to update pointers to him + void Relocate(AVLTree *to); + + // removal of the present element racine is the tree's root; it's a reference because if the + // node is the root, removal of the node will change the root + // rebalance==true if rebalancing is needed + int Remove(AVLTree * &racine, bool rebalance = true); + +private: + + AVLTree *parent; + + int balance; + + // insertion gruntwork. + int Insert(AVLTree * &racine, int insertType, AVLTree *insertL, AVLTree *insertR); + + // rebalancing functions. both are recursive, but the depth of the trees we'll use should not be a problem + // this one is for rebalancing after insertions + int RestoreBalances(AVLTree *from, AVLTree * &racine); + // this one is for removals + int RestoreBalances(int diff, AVLTree * &racine); + + // startNode is the node where the rebalancing starts; rebalancing then moves up the tree to the root + // diff is the change in "subtree height", as needed for the rebalancing + // racine is the reference to the root, since rebalancing can change it too + int Remove(AVLTree * &racine, AVLTree * &startNode, int &diff); + + void insertOn(Side s, AVLTree *of); + void insertBetween(AVLTree *l, AVLTree *r); + AVLTree *leaf(AVLTree *from, Side s); + AVLTree *leafFromParent(AVLTree *from, Side s); +}; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/AlphaLigne.cpp b/src/livarot/AlphaLigne.cpp new file mode 100644 index 0000000..7ae72c1 --- /dev/null +++ b/src/livarot/AlphaLigne.cpp @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "AlphaLigne.h" + +#include <cmath> +#include <cstdio> +#include <cstdlib> +#include <glib.h> + +AlphaLigne::AlphaLigne(int iMin,int iMax) +{ + min=iMin; + max=iMax; + if ( max < min+1 ) max=min+1; + steps=nullptr; + nbStep=maxStep=0; + before.x=min-1; + before.delta=0; + after.x=max+1; + after.delta=0; +} +AlphaLigne::~AlphaLigne() +{ + g_free(steps); + steps=nullptr; + nbStep=maxStep=0; +} +void AlphaLigne::Affiche() +{ + printf("%i steps\n",nbStep); + for (int i=0;i<nbStep;i++) { + printf("(%i %f) ",steps[i].x,steps[i].delta); // localization ok + } + printf("\n"); +} + + +void AlphaLigne::Reset() +{ + // reset to empty line + // doesn't deallocate the steps array, to minimize memory operations + curMin=max; + curMax=min; + nbStep=0; + before.x=min-1; + before.delta=0; + after.x=max+1; + after.delta=0; +} +int AlphaLigne::AddBord(float spos,float sval,float epos,float eval,float tPente) +{ +// printf("%f %f -> %f %f / %f\n",spos,sval,epos,eval,tPente); + if ( sval == eval ) return 0; + // compute the footprint of [spos,epos] on the line of pixels + float curStF=floor(spos); + float curEnF=floor(epos); + int curSt=(int)curStF; + int curEn=(int)curEnF; + + // update curMin and curMax + if ( curSt > max ) { + // we're on the right of the visible portion of the line: bail out! + if ( eval < sval ) curMax=max; + return 0; + } + if ( curSt < curMin ) curMin=curSt; + if ( ceil(epos) > curMax ) curMax=(int)ceil(epos); + + // clamp the changed portion to [min,max], no need for bigger + if ( curMax > max ) curMax=max; + if ( curMin < min ) curMin=min; + + // total amount of change in pixel coverage from before the right to after the run + float needed=eval-sval; + float needC=/*(int)ldexpf(*/needed/*,24)*/; + + if ( curEn < min ) { + // the added portion is entirely on the left, so we only have to change the initial coverage for the line + before.delta+=needC; + return 0; + } + + // add the steps + // the pixels from [curSt..curEn] (included) intersect with [spos;epos] + // since we're dealing with delta in the coverage, there is also a curEn+1 delta, since the curEn pixel intersect + // with [spos;epos] and thus has some delta with respect to its next pixel + // lots of different cases... ugly + if ( curSt == curEn ) { + if ( curSt+1 < min ) { + before.delta+=needC; + } else { + if ( nbStep+2 >= maxStep ) { + maxStep=2*nbStep+2; + steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step)); + } + float stC=/*(int)ldexpf(*/(eval-sval)*(0.5*(epos-spos)+curStF+1-epos)/*,24)*/; + steps[nbStep].x=curSt; + steps[nbStep].delta=stC; + nbStep++; + steps[nbStep].x=curSt+1; + steps[nbStep].delta=needC-stC; // au final, on a toujours le bon delta, meme avec une arete completement verticale + nbStep++; + } + } else if ( curEn == curSt+1 ) { + if ( curSt+2 < min ) { + before.delta+=needC; + } else { + if ( nbStep+3 >= maxStep ) { + maxStep=2*nbStep+3; + steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step)); + } + float stC=/*(int)ldexpf(*/0.5*tPente*(curEnF-spos)*(curEnF-spos)/*,24)*/; + float enC=/*(int)ldexpf(*/tPente-0.5*tPente*((spos-curStF)*(spos-curStF)+(curEnF+1.0-epos)*(curEnF+1.0-epos))/*,24)*/; + steps[nbStep].x=curSt; + steps[nbStep].delta=stC; + nbStep++; + steps[nbStep].x=curEn; + steps[nbStep].delta=enC; + nbStep++; + steps[nbStep].x=curEn+1; + steps[nbStep].delta=needC-stC-enC; + nbStep++; + } + } else { + float stC=/*(int)ldexpf(*/0.5*tPente*(curStF+1-spos)*(curStF+1-spos)/*,24)*/; + float stFC=/*(int)ldexpf(*/tPente-0.5*tPente*(spos-curStF)*(spos-curStF)/*,24)*/; + float enC=/*(int)ldexpf(*/tPente-0.5*tPente*(curEnF+1.0-epos)*(curEnF+1.0-epos)/*,24)*/; + float miC=/*(int)ldexpf(*/tPente/*,24)*/; + if ( curSt < min ) { + if ( curEn > max ) { + if ( nbStep+(max-min) >= maxStep ) { + maxStep=2*nbStep+(max-min); + steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step)); + } + float bfd=min-curSt-1; + bfd*=miC; + before.delta+=stC+bfd; + for (int i=min;i<max;i++) { + steps[nbStep].x=i; + steps[nbStep].delta=miC; + nbStep++; + } + } else { + if ( nbStep+(curEn-min)+2 >= maxStep ) { + maxStep=2*nbStep+(curEn-min)+2; + steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step)); + } + float bfd=min-curSt-1; + bfd*=miC; + before.delta+=stC+bfd; + for (int i=min;i<curEn;i++) { + steps[nbStep].x=i; + steps[nbStep].delta=miC; + nbStep++; + } + steps[nbStep].x=curEn; + steps[nbStep].delta=enC; + nbStep++; + steps[nbStep].x=curEn+1; + steps[nbStep].delta=needC-stC-stFC-enC-(curEn-curSt-2)*miC; + nbStep++; + } + } else { + if ( curEn > max ) { + if ( nbStep+3+(max-curSt) >= maxStep ) { + maxStep=2*nbStep+3+(curEn-curSt); + steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step)); + } + steps[nbStep].x=curSt; + steps[nbStep].delta=stC; + nbStep++; + steps[nbStep].x=curSt+1; + steps[nbStep].delta=stFC; + nbStep++; + for (int i=curSt+2;i<max;i++) { + steps[nbStep].x=i; + steps[nbStep].delta=miC; + nbStep++; + } + } else { + if ( nbStep+3+(curEn-curSt) >= maxStep ) { + maxStep=2*nbStep+3+(curEn-curSt); + steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step)); + } + steps[nbStep].x=curSt; + steps[nbStep].delta=stC; + nbStep++; + steps[nbStep].x=curSt+1; + steps[nbStep].delta=stFC; + nbStep++; + for (int i=curSt+2;i<curEn;i++) { + steps[nbStep].x=i; + steps[nbStep].delta=miC; + nbStep++; + } + steps[nbStep].x=curEn; + steps[nbStep].delta=enC; + nbStep++; + steps[nbStep].x=curEn+1; + steps[nbStep].delta=needC-stC-stFC-enC-(curEn-curSt-2)*miC; + nbStep++; + } + } + } + + return 0; +} +int AlphaLigne::AddBord(float spos,float sval,float epos,float eval) +{ + // pas de pente dans ce cas; on ajoute le delta au premier pixel + float tPente=(eval-sval); + + float curStF=floor(spos); + float curEnF=floor(epos); + int curSt=(int)curStF; + int curEn=(int)curEnF; + + if ( curSt > max ) { + if ( eval < sval ) curMax=max; + return 0; // en dehors des limites (attention a ne pas faire ca avec curEn) + } + if ( curEn < min ) { + before.delta+=eval-sval; + return 0; // en dehors des limites (attention a ne pas faire ca avec curEn) + } + + if ( curSt < curMin ) curMin=curSt; +// int curEn=(int)curEnF; + if ( ceil(epos) > curMax-1 ) curMax=1+(int)ceil(epos); + if ( curSt < min ) { + before.delta+=eval-sval; + } else { + AddRun(curSt,/*(int)ldexpf(*/(((float)(curSt+1))-spos)*tPente/*,24)*/); + AddRun(curSt+1,/*(int)ldexpf(*/(spos-((float)(curSt)))*tPente/*,24)*/); + } + return 0; +} + +void AlphaLigne::Flatten() +{ + // just sort + if ( nbStep > 0 ) qsort(steps,nbStep,sizeof(alpha_step),CmpStep); +} +void AlphaLigne::AddRun(int st,float pente) +{ + if ( nbStep >= maxStep ) { + maxStep=2*nbStep+1; + steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step)); + } + int nStep=nbStep++; + steps[nStep].x=st; + steps[nStep].delta=pente; +} + +void AlphaLigne::Raster(raster_info &dest,void* color,RasterInRunFunc worker) +{ + // start by checking if there are actually pixels in need of rasterization + if ( curMax <= curMin ) return; + if ( dest.endPix <= curMin || dest.startPix >= curMax ) return; + + int nMin=curMin,nMax=curMax; + float alpSum=before.delta; // alpSum will be the pixel coverage value, so we start at before.delta + int curStep=0; + + // first add all the deltas up to the first pixel in need of rasterization + while ( curStep < nbStep && steps[curStep].x < nMin ) { + alpSum+=steps[curStep].delta; + curStep++; + } + // just in case, if the line bounds are greater than the buffer bounds. + if ( nMin < dest.startPix ) { + for (;( curStep < nbStep && steps[curStep].x < dest.startPix) ;curStep++) alpSum+=steps[curStep].delta; + nMin=dest.startPix; + } + if ( nMax > dest.endPix ) nMax=dest.endPix; + + // raster! + int curPos=dest.startPix; + for (;curStep<nbStep;curStep++) { + if ( alpSum > 0 && steps[curStep].x > curPos ) { + // we're going to change the pixel position curPos, and alpSum is > 0: rasterization needed from + // the last position (curPos) up to the pixel we're moving to (steps[curStep].x) + int nst=curPos,nen=steps[curStep].x; +//Buffer::RasterRun(dest,color,nst,alpSum,nen,alpSum); + (worker)(dest,color,nst,alpSum,nen,alpSum); + } + // add coverage deltas + alpSum+=steps[curStep].delta; + curPos=steps[curStep].x; + if ( curPos >= nMax ) break; + } + // if we ended the line with alpSum > 0, we need to raster from curPos to the right edge + if ( alpSum > 0 && curPos < nMax ) { + int nst=curPos,nen=max; + (worker)(dest,color,nst,alpSum,nen,alpSum); +//Buffer::RasterRun(dest,color,nst,alpSum,nen,alpSum); + } +} diff --git a/src/livarot/AlphaLigne.h b/src/livarot/AlphaLigne.h new file mode 100644 index 0000000..a192e1c --- /dev/null +++ b/src/livarot/AlphaLigne.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef my_alpha_ligne +#define my_alpha_ligne + +#include "LivarotDefs.h" + +/* + * pixel coverage of a line, libart style: each pixel coverage is obtained from the coverage of the previous one by + * adding a delta given by a step. the goal is to have only a limited number of positions where the delta != 0, so that + * you only have to store a limited number of steps. + */ + +// a step +struct alpha_step { + int x; // position + float delta; // increase or decrease in pixel coverage with respect to the coverage of the previous pixel +}; + + +class AlphaLigne { +public: + // bounds of the line + // necessary since the visible portion of the canvas is bounded, and you need to compute + // the value of the pixel "just before the visible portion of the line" + int min,max; + int length; + + // before is the step containing the delta relative to a pixel infinitely far on the left of the line + // thus the initial pixel coverage is before.delta + alpha_step before,after; + // array of steps + int nbStep,maxStep; + alpha_step* steps; + + // bounds of the portion of the line that has received some coverage + int curMin,curMax; + + // iMin and iMax are the bounds of the visible portion of the line + AlphaLigne(int iMin,int iMax); + virtual ~AlphaLigne(); + + // empties the line + void Reset(); + + // add some coverage. + // pente is (eval-sval)/(epos-spos), because you can compute it once per edge, and thus spare the + // CPU some potentially costly divisions + int AddBord(float spos,float sval,float epos,float eval,float iPente); + // version where you don't have the pente parameter + int AddBord(float spos,float sval,float epos,float eval); + + // sorts the steps in increasing order. needed before you raster the line + void Flatten(); + + // debug dump of the steps + void Affiche(); + + // private + void AddRun(int st,float pente); + + // raster the line in the buffer given in "dest", with the rasterization primitive worker + // worker() is given the color parameter each time it is called. the type of the function is + // defined in LivarotDefs.h + void Raster(raster_info &dest,void* color,RasterInRunFunc worker); + + // also private. that's the comparison function given to qsort() + static int CmpStep(const void * p1, const void * p2) { + alpha_step* d1=(alpha_step*)p1; + alpha_step* d2=(alpha_step*)p2; + return d1->x - d2->x ; + }; +}; + + +#endif + diff --git a/src/livarot/BitLigne.cpp b/src/livarot/BitLigne.cpp new file mode 100644 index 0000000..2e44359 --- /dev/null +++ b/src/livarot/BitLigne.cpp @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "BitLigne.h" + +#include <cmath> +#include <cstring> +#include <cstdlib> +#include <string> +#include <cmath> +#include <cstdio> +#include <glib.h> + +BitLigne::BitLigne(int ist,int ien,float iScale) +{ + scale=iScale; + invScale=1/iScale; + st=ist; + en=ien; + if ( en <= st ) en=st+1; + stBit=(int)floor(((float)st)*invScale); // round to pixel boundaries in the canvas + enBit=(int)ceil(((float)en)*invScale); + int nbBit=enBit-stBit; + if ( nbBit&31 ) { + nbInt=nbBit/32+1; + } else { + nbInt=nbBit/32; + } + nbInt+=1; + fullB=(uint32_t*)g_malloc(nbInt*sizeof(uint32_t)); + partB=(uint32_t*)g_malloc(nbInt*sizeof(uint32_t)); + + curMin=en; + curMax=st; +} +BitLigne::~BitLigne() +{ + g_free(fullB); + g_free(partB); +} + +void BitLigne::Reset() +{ + curMin=en; + curMax=st+1; + memset(fullB,0,nbInt*sizeof(uint32_t)); + memset(partB,0,nbInt*sizeof(uint32_t)); +} +int BitLigne::AddBord(float spos,float epos,bool full) +{ + if ( spos >= epos ) return 0; + + // separation of full and not entirely full bits is a bit useless + // the goal is to obtain a set of bits that are "on the edges" of the polygon, so that their coverage + // will be 1/2 on the average. in practice it's useless for anything but the even-odd fill rule + int ffBit,lfBit; // first and last bit of the portion of the line that is entirely covered + ffBit=(int)(ceil(invScale*spos)); + lfBit=(int)(floor(invScale*epos)); + int fpBit,lpBit; // first and last bit of the portion of the line that is not entirely but partially covered + fpBit=(int)(floor(invScale*spos)); + lpBit=(int)(ceil(invScale*epos)); + + // update curMin and curMax to reflect the start and end pixel that need to be updated on the canvas + if ( floor(spos) < curMin ) curMin=(int)floor(spos); + if ( ceil(epos) > curMax ) curMax=(int)ceil(epos); + + // clamp to the line + if ( ffBit < stBit ) ffBit=stBit; + if ( ffBit > enBit ) ffBit=enBit; + if ( lfBit < stBit ) lfBit=stBit; + if ( lfBit > enBit ) lfBit=enBit; + if ( fpBit < stBit ) fpBit=stBit; + if ( fpBit > enBit ) fpBit=enBit; + if ( lpBit < stBit ) lpBit=stBit; + if ( lpBit > enBit ) lpBit=enBit; + + // offset to get actual bit position in the array + ffBit-=stBit; + lfBit-=stBit; + fpBit-=stBit; + lpBit-=stBit; + + // get the end and start indices of the elements of fullB and partB that will receives coverage + int ffPos=ffBit>>5; + int lfPos=lfBit>>5; + int fpPos=fpBit>>5; + int lpPos=lpBit>>5; + // get bit numbers in the last and first changed elements of the fullB and partB arrays + int ffRem=ffBit&31; + int lfRem=lfBit&31; + int fpRem=fpBit&31; + int lpRem=lpBit&31; + // add the coverage + // note that the "full" bits are always a subset of the "not empty" bits, ie of the partial bits + // the function is a bit lame: since there is at most one bit that is partial but not full, or no full bit, + // it does 2 times the optimal amount of work when the coverage is full. but i'm too lazy to change that... + if ( fpPos == lpPos ) { // only one element of the arrays is modified + // compute the vector of changed bits in the element + uint32_t add=0xFFFFFFFF; + if ( lpRem < 32 ) {add>>=32-lpRem;add<<=32-lpRem; } + if ( lpRem <= 0 ) add=0; + if ( fpRem > 0) {add<<=fpRem;add>>=fpRem;} + // and put it in the line + fullB[fpPos]&=~(add); // partial is exclusive from full, so partial bits are removed from fullB + partB[fpPos]|=add; // and added to partB + if ( full ) { // if the coverage is full, add the vector of full bits + if ( ffBit <= lfBit ) { + add=0xFFFFFFFF; + if ( lfRem < 32 ) {add>>=32-lfRem;add<<=32-lfRem;} + if ( lfRem <= 0 ) add=0; + if ( ffRem > 0 ) {add<<=ffRem;add>>=ffRem;} + fullB[ffPos]|=add; + partB[ffPos]&=~(add); + } + } + } else { + // first and last elements are differents, so add what appropriate to each + uint32_t add=0xFFFFFFFF; + if ( fpRem > 0 ) {add<<=fpRem;add>>=fpRem;} + fullB[fpPos]&=~(add); + partB[fpPos]|=add; + + add=0xFFFFFFFF; + if ( lpRem < 32 ) {add>>=32-lpRem;add<<=32-lpRem;} + if ( lpRem <= 0 ) add=0; + fullB[lpPos]&=~(add); + partB[lpPos]|=add; + + // and fill what's in between with partial bits + if ( lpPos > fpPos+1 ) memset(fullB+(fpPos+1),0x00,(lpPos-fpPos-1)*sizeof(uint32_t)); + if ( lpPos > fpPos+1 ) memset(partB+(fpPos+1),0xFF,(lpPos-fpPos-1)*sizeof(uint32_t)); + + if ( full ) { // is the coverage is full, do your magic + if ( ffBit <= lfBit ) { + if ( ffPos == lfPos ) { + add=0xFFFFFFFF; + if ( lfRem < 32 ) {add>>=32-lfRem;add<<=32-lfRem;} + if ( lfRem <= 0 ) add=0; + if ( ffRem > 0 ) {add<<=ffRem;add>>=ffRem;} + fullB[ffPos]|=add; + partB[ffPos]&=~(add); + } else { + add=0xFFFFFFFF; + if ( ffRem > 0 ) {add<<=ffRem;add>>=ffRem;} + fullB[ffPos]|=add; + partB[ffPos]&=~add; + + add=0xFFFFFFFF; + if ( lfRem < 32 ) {add>>=32-lfRem;add<<=32-lfRem;} + if ( lfRem <= 0 ) add=0; + fullB[lfPos]|=add; + partB[lfPos]&=~add; + + if ( lfPos > ffPos+1 ) memset(fullB+(ffPos+1),0xFF,(lfPos-ffPos-1)*sizeof(uint32_t)); + if ( lfPos > ffPos+1 ) memset(partB+(ffPos+1),0x00,(lfPos-ffPos-1)*sizeof(uint32_t)); + } + } + } + } + return 0; +} + + +void BitLigne::Affiche() +{ + for (int i=0;i<nbInt;i++) printf(" %.8x",fullB[i]); + printf("\n"); + for (int i=0;i<nbInt;i++) printf(" %.8x",partB[i]); + printf("\n\n"); +} + diff --git a/src/livarot/BitLigne.h b/src/livarot/BitLigne.h new file mode 100644 index 0000000..7a7fcbe --- /dev/null +++ b/src/livarot/BitLigne.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef my_bit_ligne +#define my_bit_ligne + +#include "LivarotDefs.h" + +/* + * a line of bits used for rasterizations of polygons + * the Scan() and QuickScan() functions fill the line with bits; after that you can use the Copy() function + * of the IntLigne class to have a set of pixel coverage runs + */ + +class BitLigne { +public: + // start and end pixels of the line + int st,en; + // start and end bits of the line + int stBit,enBit; + // size of the fullB and partB arrays + int nbInt; + // arrays of uint32_t used to store the bits + // bits of fullB mean "this pixel/bit is entirely covered" + // bits of partB mean "this pixel/bit is not entirely covered" (a better use would be: "this pixel is at least partially covered) + // so it's in fact a triage mask + uint32_t* fullB; + uint32_t* partB; + + // when adding bits, these 2 values are updated to reflect which portion of the line has received coverage + int curMin,curMax; + // invScale is: canvas -> bit in the line + // scale is: bit -> canvas, ie the size (width) of a bit + float scale,invScale; + + BitLigne(int ist,int ien,float iScale=0.25); // default scale is 1/4 for 4x4 supersampling + virtual ~BitLigne(); + + // reset the line to full empty + void Reset(); + + // put coverage from spos to epos (in canvas coordinates) + // full==true means that the bits from (fractional) position spos to epos are entirely covered + // full==false means the bits are not entirely covered, ie this is an edge + // see the Scan() and AvanceEdge() functions to see the difference + int AddBord(float spos,float epos,bool full); + + // debug dump + void Affiche(); + +}; + +#endif + + diff --git a/src/livarot/CMakeLists.txt b/src/livarot/CMakeLists.txt new file mode 100644 index 0000000..398f7a2 --- /dev/null +++ b/src/livarot/CMakeLists.txt @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +set(livarot_SRC + AlphaLigne.cpp + AVL.cpp + BitLigne.cpp + float-line.cpp + int-line.cpp + PathConversion.cpp + Path.cpp + PathCutting.cpp + path-description.cpp + PathOutline.cpp + PathSimplify.cpp + PathStroke.cpp + Shape.cpp + ShapeDraw.cpp + ShapeMisc.cpp + ShapeRaster.cpp + ShapeSweep.cpp + sweep-event.cpp + sweep-tree.cpp + sweep-tree-list.cpp + + + # ------- + # Headers + AVL.h + AlphaLigne.h + BitLigne.h + LivarotDefs.h + Path.h + Shape.h + float-line.h + int-line.h + path-description.h + sweep-event-queue.h + sweep-event.h + sweep-tree-list.h + sweep-tree.h +) + +add_inkscape_lib(livarot_LIB "${livarot_SRC}") +target_link_libraries(livarot_LIB PUBLIC 2Geom::2geom)
\ No newline at end of file diff --git a/src/livarot/LivarotDefs.h b/src/livarot/LivarotDefs.h new file mode 100644 index 0000000..9940cde --- /dev/null +++ b/src/livarot/LivarotDefs.h @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef my_defs +#define my_defs + +#include <cstdint> + +// error codes (mostly obsolete) +enum +{ + avl_no_err = 0, // 0 is the error code for "everything OK" + avl_bal_err = 1, + avl_rm_err = 2, + avl_ins_err = 3, + shape_euler_err = 4, // computations result in a non-eulerian graph, thus the function cannot do a proper polygon + // despite the rounding sheme, this still happen with uber-complex graphs + // note that coordinates are stored in double => double precision for the computation is not even + // enough to get exact results (need quadruple precision, i think). + shape_input_err = 5, // the function was given an incorrect input (not a polygon, or not eulerian) + shape_nothing_to_do = 6 // the function had nothing to do (zero offset, etc) +}; + +// return codes for the find function in the AVL tree (private) + +/** + * The SweepTree::Find function and its variant for a single point figure out where a point or an edge + * should be inserted in a linked list of edges. Once calculated, they return one of these values to indicate + * how that place looks like. + */ +enum +{ + not_found = 0, /*!< Didn't find a place. */ + found_exact = 1, /*!< Found such an edge where edge to insert lies directly on top of another edge */ + found_on_left = 2, /*!< Point/edge should go to the left of some edge. (There is nothing on the left of that edge) */ + found_on_right = 3, /*!< Point/edge should go to the right of some edge. (There is nothing on the right of that edge) */ + found_between = 4 /*!< Point/edge should go in between two particular edges. */ +}; + +// types of cap for stroking polylines +enum butt_typ +{ + butt_straight, // straight line + butt_square, // half square + butt_round, // half circle + butt_pointy // a little pointy hat +}; +// types of joins for stroking paths +enum join_typ +{ + join_straight, // a straight line + join_round, // arc of circle (in fact, one or two quadratic bezier curve chunks) + join_pointy // a miter join (uses the miter parameter) +}; +typedef enum butt_typ ButtType; +typedef enum join_typ JoinType; + +enum fill_typ +{ + fill_oddEven = 0, + fill_nonZero = 1, + fill_positive = 2, + fill_justDont = 3 +}; +typedef enum fill_typ FillRule; + +// info for a run of pixel to fill +struct raster_info { + int startPix,endPix; // start and end pixel from the polygon POV + int sth,stv; // coordinates for the first pixel in the run, in (possibly another) POV + uint32_t* buffer; // pointer to the first pixel in the run +}; +typedef void (*RasterInRunFunc) (raster_info &dest,void *data,int nst,float vst,int nen,float ven); // init for position ph,pv; the last parameter is a pointer + + +enum Side { + LEFT = 0, + RIGHT = 1 +}; + +enum FirstOrLast { + FIRST = 0, + LAST = 1 +}; + +#endif diff --git a/src/livarot/Path.cpp b/src/livarot/Path.cpp new file mode 100644 index 0000000..352bc0a --- /dev/null +++ b/src/livarot/Path.cpp @@ -0,0 +1,945 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glib.h> +#include "Path.h" + +#include <2geom/pathvector.h> + +#include "livarot/path-description.h" + +/* + * manipulation of the path data: path description and polyline + * grunt work... + * at the end of this file, 2 utilitary functions to get the point and tangent to path associated with a (command no;abcissis) + */ + + +Path::Path() +{ + descr_flags = 0; + pending_bezier_cmd = -1; + pending_moveto_cmd = -1; + + back = false; +} + +Path::~Path() +{ + for (auto & i : descr_cmd) { + delete i; + } +} + +// debug function do dump the path contents on stdout +void Path::Affiche() +{ + std::cout << "path: " << descr_cmd.size() << " commands." << std::endl; + for (auto i : descr_cmd) { + i->dump(std::cout); + std::cout << std::endl; + } + + std::cout << std::endl; +} + +void Path::Reset() +{ + for (auto & i : descr_cmd) { + delete i; + } + + descr_cmd.clear(); + pending_bezier_cmd = -1; + pending_moveto_cmd = -1; + descr_flags = 0; +} + +void Path::Copy(Path * who) +{ + ResetPoints(); + + for (auto & i : descr_cmd) { + delete i; + } + + descr_cmd.clear(); + + for (auto i : who->descr_cmd) + { + descr_cmd.push_back(i->clone()); + } +} + +void Path::CloseSubpath() +{ + descr_flags &= ~(descr_doing_subpath); + pending_moveto_cmd = -1; +} + +int Path::ForcePoint() +{ + if (descr_flags & descr_adding_bezier) { + EndBezierTo (); + } + + if ( (descr_flags & descr_doing_subpath) == 0 ) { + return -1; + } + + if (descr_cmd.empty()) { + return -1; + } + + descr_cmd.push_back(new PathDescrForced); + return descr_cmd.size() - 1; +} + + +void Path::InsertForcePoint(int at) +{ + if ( at < 0 || at > int(descr_cmd.size()) ) { + return; + } + + if ( at == int(descr_cmd.size()) ) { + ForcePoint(); + return; + } + + descr_cmd.insert(descr_cmd.begin() + at, new PathDescrForced); +} + +int Path::Close() +{ + if ( descr_flags & descr_adding_bezier ) { + CancelBezier(); + } + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } else { + // Nothing to close. + return -1; + } + + descr_cmd.push_back(new PathDescrClose); + + descr_flags &= ~(descr_doing_subpath); + pending_moveto_cmd = -1; + + return descr_cmd.size() - 1; +} + +int Path::MoveTo(Geom::Point const &iPt) +{ + if ( descr_flags & descr_adding_bezier ) { + EndBezierTo(iPt); + } + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } + pending_moveto_cmd = descr_cmd.size(); + + descr_cmd.push_back(new PathDescrMoveTo(iPt)); + + descr_flags |= descr_doing_subpath; + return descr_cmd.size() - 1; +} + +void Path::InsertMoveTo(Geom::Point const &iPt, int at) +{ + if ( at < 0 || at > int(descr_cmd.size()) ) { + return; + } + + if ( at == int(descr_cmd.size()) ) { + MoveTo(iPt); + return; + } + + descr_cmd.insert(descr_cmd.begin() + at, new PathDescrMoveTo(iPt)); +} + +int Path::LineTo(Geom::Point const &iPt) +{ + if (descr_flags & descr_adding_bezier) { + EndBezierTo (iPt); + } + if (!( descr_flags & descr_doing_subpath )) { + return MoveTo (iPt); + } + + descr_cmd.push_back(new PathDescrLineTo(iPt)); + return descr_cmd.size() - 1; +} + +void Path::InsertLineTo(Geom::Point const &iPt, int at) +{ + if ( at < 0 || at > int(descr_cmd.size()) ) { + return; + } + + if ( at == int(descr_cmd.size()) ) { + LineTo(iPt); + return; + } + + descr_cmd.insert(descr_cmd.begin() + at, new PathDescrLineTo(iPt)); +} + +int Path::CubicTo(Geom::Point const &iPt, Geom::Point const &iStD, Geom::Point const &iEnD) +{ + if (descr_flags & descr_adding_bezier) { + EndBezierTo(iPt); + } + if ( (descr_flags & descr_doing_subpath) == 0) { + return MoveTo (iPt); + } + + descr_cmd.push_back(new PathDescrCubicTo(iPt, iStD, iEnD)); + return descr_cmd.size() - 1; +} + + +void Path::InsertCubicTo(Geom::Point const &iPt, Geom::Point const &iStD, Geom::Point const &iEnD, int at) +{ + if ( at < 0 || at > int(descr_cmd.size()) ) { + return; + } + + if ( at == int(descr_cmd.size()) ) { + CubicTo(iPt,iStD,iEnD); + return; + } + + descr_cmd.insert(descr_cmd.begin() + at, new PathDescrCubicTo(iPt, iStD, iEnD)); +} + +int Path::ArcTo(Geom::Point const &iPt, double iRx, double iRy, double angle, + bool iLargeArc, bool iClockwise) +{ + if (descr_flags & descr_adding_bezier) { + EndBezierTo(iPt); + } + if ( (descr_flags & descr_doing_subpath) == 0 ) { + return MoveTo(iPt); + } + + descr_cmd.push_back(new PathDescrArcTo(iPt, iRx, iRy, angle, iLargeArc, iClockwise)); + return descr_cmd.size() - 1; +} + + +void Path::InsertArcTo(Geom::Point const &iPt, double iRx, double iRy, double angle, + bool iLargeArc, bool iClockwise, int at) +{ + if ( at < 0 || at > int(descr_cmd.size()) ) { + return; + } + + if ( at == int(descr_cmd.size()) ) { + ArcTo(iPt, iRx, iRy, angle, iLargeArc, iClockwise); + return; + } + + descr_cmd.insert(descr_cmd.begin() + at, new PathDescrArcTo(iPt, iRx, iRy, + angle, iLargeArc, iClockwise)); +} + +int Path::TempBezierTo() +{ + if (descr_flags & descr_adding_bezier) { + CancelBezier(); + } + if ( (descr_flags & descr_doing_subpath) == 0) { + // No starting point -> bad. + return -1; + } + pending_bezier_cmd = descr_cmd.size(); + + descr_cmd.push_back(new PathDescrBezierTo(Geom::Point(0, 0), 0)); + descr_flags |= descr_adding_bezier; + descr_flags |= descr_delayed_bezier; + return descr_cmd.size() - 1; +} + +void Path::CancelBezier() +{ + descr_flags &= ~(descr_adding_bezier); + descr_flags &= ~(descr_delayed_bezier); + if (pending_bezier_cmd < 0) { + return; + } + + /* FIXME: I think there's a memory leak here */ + descr_cmd.resize(pending_bezier_cmd); + pending_bezier_cmd = -1; +} + +int Path::EndBezierTo() +{ + if (descr_flags & descr_delayed_bezier) { + CancelBezier (); + } else { + pending_bezier_cmd = -1; + descr_flags &= ~(descr_adding_bezier); + descr_flags &= ~(descr_delayed_bezier); + } + return -1; +} + +int Path::EndBezierTo(Geom::Point const &iPt) +{ + if ( (descr_flags & descr_adding_bezier) == 0 ) { + return LineTo(iPt); + } + if ( (descr_flags & descr_doing_subpath) == 0 ) { + return MoveTo(iPt); + } + if ( (descr_flags & descr_delayed_bezier) == 0 ) { + return EndBezierTo(); + } + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[pending_bezier_cmd]); + nData->p = iPt; + pending_bezier_cmd = -1; + descr_flags &= ~(descr_adding_bezier); + descr_flags &= ~(descr_delayed_bezier); + return -1; +} + + +int Path::IntermBezierTo(Geom::Point const &iPt) +{ + if ( (descr_flags & descr_adding_bezier) == 0 ) { + return LineTo (iPt); + } + + if ( (descr_flags & descr_doing_subpath) == 0) { + return MoveTo (iPt); + } + + descr_cmd.push_back(new PathDescrIntermBezierTo(iPt)); + + PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[pending_bezier_cmd]); + nBData->nb++; + return descr_cmd.size() - 1; +} + + +void Path::InsertIntermBezierTo(Geom::Point const &iPt, int at) +{ + if ( at < 0 || at > int(descr_cmd.size()) ) { + return; + } + + if ( at == int(descr_cmd.size()) ) { + IntermBezierTo(iPt); + return; + } + + descr_cmd.insert(descr_cmd.begin() + at, new PathDescrIntermBezierTo(iPt)); +} + + +int Path::BezierTo(Geom::Point const &iPt) +{ + if ( descr_flags & descr_adding_bezier ) { + EndBezierTo(iPt); + } + + if ( (descr_flags & descr_doing_subpath) == 0 ) { + return MoveTo (iPt); + } + + pending_bezier_cmd = descr_cmd.size(); + + descr_cmd.push_back(new PathDescrBezierTo(iPt, 0)); + descr_flags |= descr_adding_bezier; + descr_flags &= ~(descr_delayed_bezier); + return descr_cmd.size() - 1; +} + + +void Path::InsertBezierTo(Geom::Point const &iPt, int iNb, int at) +{ + if ( at < 0 || at > int(descr_cmd.size()) ) { + return; + } + + if ( at == int(descr_cmd.size()) ) { + BezierTo(iPt); + return; + } + + descr_cmd.insert(descr_cmd.begin() + at, new PathDescrBezierTo(iPt, iNb)); +} + + +/* + * points of the polyline + */ +void +Path::SetBackData (bool nVal) +{ + if (! back) { + if (nVal) { + back = true; + ResetPoints(); + } + } else { + if (! nVal) { + back = false; + ResetPoints(); + } + } +} + + +void Path::ResetPoints() +{ + pts.clear(); +} + + +int Path::AddPoint(Geom::Point const &iPt, bool mvto) +{ + if (back) { + return AddPoint (iPt, -1, 0.0, mvto); + } + + if ( !mvto && !pts.empty() && pts.back().p == iPt ) { + return -1; + } + + int const n = pts.size(); + pts.emplace_back(mvto ? polyline_moveto : polyline_lineto, iPt); + return n; +} + + +int Path::ReplacePoint(Geom::Point const &iPt) +{ + if (pts.empty()) { + return -1; + } + + int const n = pts.size() - 1; + pts[n] = path_lineto(polyline_lineto, iPt); + return n; +} + + +int Path::AddPoint(Geom::Point const &iPt, int ip, double it, bool mvto) +{ + if (! back) { + return AddPoint (iPt, mvto); + } + + if ( !mvto && !pts.empty() && pts.back().p == iPt ) { + return -1; + } + + int const n = pts.size(); + pts.emplace_back(mvto ? polyline_moveto : polyline_lineto, iPt, ip, it); + return n; +} + +int Path::AddForcedPoint(Geom::Point const &iPt) +{ + if (back) { + return AddForcedPoint (iPt, -1, 0.0); + } + + if ( pts.empty() || pts.back().isMoveTo != polyline_lineto ) { + return -1; + } + + int const n = pts.size(); + pts.emplace_back(polyline_forced, pts[n - 1].p); + return n; +} + + +int Path::AddForcedPoint(Geom::Point const &iPt, int /*ip*/, double /*it*/) +{ + /* FIXME: ip & it aren't used. Is this deliberate? */ + if (!back) { + return AddForcedPoint (iPt); + } + + if ( pts.empty() || pts.back().isMoveTo != polyline_lineto ) { + return -1; + } + + int const n = pts.size(); + pts.emplace_back(polyline_forced, pts[n - 1].p, pts[n - 1].piece, pts[n - 1].t); + return n; +} + +void Path::PolylineBoundingBox(double &l, double &t, double &r, double &b) +{ + l = t = r = b = 0.0; + if ( pts.empty() ) { + return; + } + + std::vector<path_lineto>::const_iterator i = pts.begin(); + l = r = i->p[Geom::X]; + t = b = i->p[Geom::Y]; + ++i; + + for (; i != pts.end(); ++i) { + r = std::max(r, i->p[Geom::X]); + l = std::min(l, i->p[Geom::X]); + b = std::max(b, i->p[Geom::Y]); + t = std::min(t, i->p[Geom::Y]); + } +} + + +/** + * \param piece Index of a one of our commands. + * \param at Distance along the segment that corresponds to `piece' (0 <= at <= 1) + * \param pos Filled in with the point at `at' on `piece'. + */ + +void Path::PointAt(int piece, double at, Geom::Point &pos) +{ + if (piece < 0 || piece >= int(descr_cmd.size())) { + // this shouldn't happen: the piece we are asked for doesn't + // exist in the path + pos = Geom::Point(0,0); + return; + } + + PathDescr const *theD = descr_cmd[piece]; + int const typ = theD->getType(); + Geom::Point tgt; + double len; + double rad; + + if (typ == descr_moveto) { + + return PointAt (piece + 1, 0.0, pos); + + } else if (typ == descr_close || typ == descr_forced) { + + return PointAt (piece - 1, 1.0, pos); + + } else if (typ == descr_lineto) { + + PathDescrLineTo const *nData = dynamic_cast<PathDescrLineTo const *>(theD); + TangentOnSegAt(at, PrevPoint (piece - 1), *nData, pos, tgt, len); + + } else if (typ == descr_arcto) { + + PathDescrArcTo const *nData = dynamic_cast<PathDescrArcTo const *>(theD); + TangentOnArcAt(at,PrevPoint (piece - 1), *nData, pos, tgt, len, rad); + + } else if (typ == descr_cubicto) { + + PathDescrCubicTo const *nData = dynamic_cast<PathDescrCubicTo const *>(theD); + TangentOnCubAt(at, PrevPoint (piece - 1), *nData, false, pos, tgt, len, rad); + + } else if (typ == descr_bezierto || typ == descr_interm_bezier) { + + int bez_st = piece; + while (bez_st >= 0) { + int nt = descr_cmd[bez_st]->getType(); + if (nt == descr_bezierto) + break; + bez_st--; + } + if ( bez_st < 0 ) { + // Didn't find the beginning of the spline (bad). + // [pas trouvé le dubut de la spline (mauvais)] + return PointAt(piece - 1, 1.0, pos); + } + + PathDescrBezierTo *stB = dynamic_cast<PathDescrBezierTo *>(descr_cmd[bez_st]); + if ( piece > bez_st + stB->nb ) { + // The spline goes past the authorized number of commands (bad). + // [la spline sort du nombre de commandes autorisé (mauvais)] + return PointAt(piece - 1, 1.0, pos); + } + + int k = piece - bez_st; + Geom::Point const bStPt = PrevPoint(bez_st - 1); + if (stB->nb == 1 || k <= 0) { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 1]); + TangentOnBezAt(at, bStPt, *nData, *stB, false, pos, tgt, len, rad); + } else { + // forcement plus grand que 1 + if (k == 1) { + PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 1]); + PathDescrIntermBezierTo *nnextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 2]); + PathDescrBezierTo fin(0.5 * (nextI->p + nnextI->p), 1); + TangentOnBezAt(at, bStPt, *nextI, fin, false, pos, tgt, len, rad); + } else if (k == stB->nb) { + PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k]); + PathDescrIntermBezierTo *prevI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k - 1]); + Geom::Point stP = 0.5 * ( prevI->p + nextI->p ); + TangentOnBezAt(at, stP, *nextI, *stB, false, pos, tgt, len, rad); + } else { + PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k]); + PathDescrIntermBezierTo *prevI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k - 1]); + PathDescrIntermBezierTo *nnextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k + 1]); + Geom::Point stP = 0.5 * ( prevI->p + nextI->p ); + PathDescrBezierTo fin(0.5 * (nextI->p + nnextI->p), 1); + TangentOnBezAt(at, stP, *nextI, fin, false, pos, tgt, len, rad); + } + } + } +} + + +void Path::PointAndTangentAt(int piece, double at, Geom::Point &pos, Geom::Point &tgt) +{ + if (piece < 0 || piece >= int(descr_cmd.size())) { + // this shouldn't happen: the piece we are asked for doesn't exist in the path + pos = Geom::Point(0, 0); + return; + } + + PathDescr const *theD = descr_cmd[piece]; + int typ = theD->getType(); + double len; + double rad; + if (typ == descr_moveto) { + + return PointAndTangentAt(piece + 1, 0.0, pos, tgt); + + } else if (typ == descr_close ) { + + int cp = piece - 1; + while ( cp >= 0 && (descr_cmd[cp]->getType()) != descr_moveto ) { + cp--; + } + if ( cp >= 0 ) { + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[cp]); + PathDescrLineTo dst(nData->p); + TangentOnSegAt(at, PrevPoint (piece - 1), dst, pos, tgt, len); + } + + } else if ( typ == descr_forced) { + + return PointAndTangentAt(piece - 1, 1.0, pos,tgt); + + } else if (typ == descr_lineto) { + + PathDescrLineTo const *nData = dynamic_cast<PathDescrLineTo const *>(theD); + TangentOnSegAt(at, PrevPoint (piece - 1), *nData, pos, tgt, len); + + } else if (typ == descr_arcto) { + + PathDescrArcTo const *nData = dynamic_cast<PathDescrArcTo const *>(theD); + TangentOnArcAt (at,PrevPoint (piece - 1), *nData, pos, tgt, len, rad); + + } else if (typ == descr_cubicto) { + + PathDescrCubicTo const *nData = dynamic_cast<PathDescrCubicTo const *>(theD); + TangentOnCubAt (at, PrevPoint (piece - 1), *nData, false, pos, tgt, len, rad); + + } else if (typ == descr_bezierto || typ == descr_interm_bezier) { + int bez_st = piece; + while (bez_st >= 0) { + int nt = descr_cmd[bez_st]->getType(); + if (nt == descr_bezierto) break; + bez_st--; + } + if ( bez_st < 0 ) { + return PointAndTangentAt(piece - 1, 1.0, pos, tgt); + // Didn't find the beginning of the spline (bad). + // [pas trouvé le dubut de la spline (mauvais)] + } + + PathDescrBezierTo* stB = dynamic_cast<PathDescrBezierTo*>(descr_cmd[bez_st]); + if ( piece > bez_st + stB->nb ) { + return PointAndTangentAt(piece - 1, 1.0, pos, tgt); + // The spline goes past the number of authorized commands (bad). + // [la spline sort du nombre de commandes autorisé (mauvais)] + } + + int k = piece - bez_st; + Geom::Point const bStPt(PrevPoint( bez_st - 1 )); + if (stB->nb == 1 || k <= 0) { + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 1]); + TangentOnBezAt (at, bStPt, *nData, *stB, false, pos, tgt, len, rad); + } else { + // forcement plus grand que 1 + if (k == 1) { + PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 1]); + PathDescrIntermBezierTo *nnextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 2]); + PathDescrBezierTo fin(0.5 * (nextI->p + nnextI->p), 1); + TangentOnBezAt(at, bStPt, *nextI, fin, false, pos, tgt, len, rad); + } else if (k == stB->nb) { + PathDescrIntermBezierTo *prevI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k - 1]); + PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k]); + Geom::Point stP = 0.5 * ( prevI->p + nextI->p ); + TangentOnBezAt(at, stP, *nextI, *stB, false, pos, tgt, len, rad); + } else { + PathDescrIntermBezierTo *prevI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k - 1]); + PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k]); + PathDescrIntermBezierTo *nnextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k + 1]); + Geom::Point stP = 0.5 * ( prevI->p + nextI->p ); + PathDescrBezierTo fin(0.5 * (nnextI->p + nnextI->p), 1); + TangentOnBezAt(at, stP, *nextI, fin, false, pos, tgt, len, rad); + } + } + } +} + +/** + * Apply a transform in-place. + * + * Note: Converts to Geom::PathVector, applies the transform, and converts back. + */ +void Path::Transform(const Geom::Affine &trans) +{ + LoadPathVector(MakePathVector() * trans); +} + +void Path::FastBBox(double &l,double &t,double &r,double &b) +{ + l = t = r = b = 0; + bool empty = true; + Geom::Point lastP(0, 0); + + for (auto & i : descr_cmd) { + int const typ = i->getType(); + switch ( typ ) { + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(i); + if ( empty ) { + l = r = nData->p[Geom::X]; + t = b = nData->p[Geom::Y]; + empty = false; + } else { + if ( nData->p[Geom::X] < l ) { + l = nData->p[Geom::X]; + } + if ( nData->p[Geom::X] > r ) { + r = nData->p[Geom::X]; + } + if ( nData->p[Geom::Y] < t ) { + t = nData->p[Geom::Y]; + } + if ( nData->p[Geom::Y] > b ) { + b = nData->p[Geom::Y]; + } + } + lastP = nData->p; + } + break; + + case descr_moveto: + { + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(i); + if ( empty ) { + l = r = nData->p[Geom::X]; + t = b = nData->p[Geom::Y]; + empty = false; + } else { + if ( nData->p[Geom::X] < l ) { + l = nData->p[Geom::X]; + } + if ( nData->p[Geom::X] > r ) { + r = nData->p[Geom::X]; + } + if ( nData->p[Geom::Y] < t ) { + t = nData->p[Geom::Y]; + } + if ( nData->p[Geom::Y] > b ) { + b = nData->p[Geom::Y]; + } + } + lastP = nData->p; + } + break; + + case descr_arcto: + { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(i); + if ( empty ) { + l = r = nData->p[Geom::X]; + t = b = nData->p[Geom::Y]; + empty = false; + } else { + if ( nData->p[Geom::X] < l ) { + l = nData->p[Geom::X]; + } + if ( nData->p[Geom::X] > r ) { + r = nData->p[Geom::X]; + } + if ( nData->p[Geom::Y] < t ) { + t = nData->p[Geom::Y]; + } + if ( nData->p[Geom::Y] > b ) { + b = nData->p[Geom::Y]; + } + } + lastP = nData->p; + } + break; + + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(i); + if ( empty ) { + l = r = nData->p[Geom::X]; + t = b = nData->p[Geom::Y]; + empty = false; + } else { + if ( nData->p[Geom::X] < l ) { + l = nData->p[Geom::X]; + } + if ( nData->p[Geom::X] > r ) { + r = nData->p[Geom::X]; + } + if ( nData->p[Geom::Y] < t ) { + t = nData->p[Geom::Y]; + } + if ( nData->p[Geom::Y] > b ) { + b = nData->p[Geom::Y]; + } + } + +/* bug 249665: "...the calculation of the bounding-box for cubic-paths +has some extra steps to make it work correctly in Win32 that unfortunately +are unnecessary in Linux, generating wrong results. This only shows in +Type1 fonts because they use cubic-paths instead of the +bezier-paths used by True-Type fonts." +*/ + +#ifdef _WIN32 + Geom::Point np = nData->p - nData->end; + if ( np[Geom::X] < l ) { + l = np[Geom::X]; + } + if ( np[Geom::X] > r ) { + r = np[Geom::X]; + } + if ( np[Geom::Y] < t ) { + t = np[Geom::Y]; + } + if ( np[Geom::Y] > b ) { + b = np[Geom::Y]; + } + + np = lastP + nData->start; + if ( np[Geom::X] < l ) { + l = np[Geom::X]; + } + if ( np[Geom::X] > r ) { + r = np[Geom::X]; + } + if ( np[Geom::Y] < t ) { + t = np[Geom::Y]; + } + if ( np[Geom::Y] > b ) { + b = np[Geom::Y]; + } +#endif + + lastP = nData->p; + } + break; + + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(i); + if ( empty ) { + l = r = nData->p[Geom::X]; + t = b = nData->p[Geom::Y]; + empty = false; + } else { + if ( nData->p[Geom::X] < l ) { + l = nData->p[Geom::X]; + } + if ( nData->p[Geom::X] > r ) { + r = nData->p[Geom::X]; + } + if ( nData->p[Geom::Y] < t ) { + t = nData->p[Geom::Y]; + } + if ( nData->p[Geom::Y] > b ) { + b = nData->p[Geom::Y]; + } + } + lastP = nData->p; + } + break; + + case descr_interm_bezier: + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(i); + if ( empty ) { + l = r = nData->p[Geom::X]; + t = b = nData->p[Geom::Y]; + empty = false; + } else { + if ( nData->p[Geom::X] < l ) { + l = nData->p[Geom::X]; + } + if ( nData->p[Geom::X] > r ) { + r = nData->p[Geom::X]; + } + if ( nData->p[Geom::Y] < t ) { + t = nData->p[Geom::Y]; + } + if ( nData->p[Geom::Y] > b ) { + b = nData->p[Geom::Y]; + } + } + } + break; + } + } +} + +char *Path::svg_dump_path() const +{ + Inkscape::SVGOStringStream os; + + for (int i = 0; i < int(descr_cmd.size()); i++) { + Geom::Point const p = (i == 0) ? Geom::Point(0, 0) : PrevPoint(i - 1); + descr_cmd[i]->dumpSVG(os, p); + } + + return g_strdup (os.str().c_str()); +} + +// Find out if the segment that corresponds to 'piece' is a straight line +bool Path::IsLineSegment(int piece) +{ + if (piece < 0 || piece >= int(descr_cmd.size())) { + return false; + } + + PathDescr const *theD = descr_cmd[piece]; + int const typ = theD->getType(); + + return (typ == descr_lineto); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/livarot/Path.h b/src/livarot/Path.h new file mode 100644 index 0000000..f61451d --- /dev/null +++ b/src/livarot/Path.h @@ -0,0 +1,1001 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2014 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* + * Path.h + * nlivarot + * + * Created by fred on Tue Jun 17 2003. + * + */ + +#ifndef my_path +#define my_path + +#include <vector> +#include "LivarotDefs.h" +#include <2geom/point.h> + +struct PathDescr; +struct PathDescrLineTo; +struct PathDescrArcTo; +struct PathDescrCubicTo; +struct PathDescrBezierTo; +struct PathDescrIntermBezierTo; + +class SPStyle; + +/* + * the Path class: a structure to hold path description and their polyline approximation (not kept in sync) + * the path description is built with regular commands like MoveTo() LineTo(), etc + * the polyline approximation is built by a call to Convert() or its variants + * another possibility would be to call directly the AddPoint() functions, but that is not encouraged + * the conversion to polyline can salvage data as to where on the path each polyline's point lies; use + * ConvertWithBackData() for this. after this call, it's easy to rewind the polyline: sequences of points + * of the same path command can be reassembled in a command + */ + +// polyline description commands +enum +{ + polyline_lineto = 0, // a lineto + polyline_moveto = 1, // a moveto + polyline_forced = 2 // a forced point, ie a point that was an angle or an intersection in a previous life + // or more realistically a control point in the path description that created the polyline + // forced points are used as "breakable" points for the polyline -> cubic bezier patch operations + // each time the bezier fitter encounters such a point in the polyline, it decreases its treshhold, + // so that it is more likely to cut the polyline at that position and produce a bezier patch +}; + +class Shape; + +// path creation: 2 phases: first the path is given as a succession of commands (MoveTo, LineTo, CurveTo...); then it +// is converted in a polyline +// a polylone can be stroked or filled to make a polygon + +/** + * Path and its polyline approximation. + * + * A Path is exactly analogous to an SVG path element. Like the SVG path element, this class + * stores path commands. A Path can be approximated by line segments and this approximation + * is known as a "polyline approximation". Internally, the polyline approximation is stored + * as a set of points. + * + * Each path command (except the MoveTo), creates a new segment. A path segment can be defined + * as a function of time over the interval [0, 1]. Each point in the polyline approximation can + * store the index of the path command that created the path segment that it came from and the time + * value at which it existed. The midpoint of a line segment would be at \f[ t = 0.5 \f] for + * example. This information is known as "back data" since it preserves the information about the + * original segments that existed in the path and can help us recreate them or their portions back. + * Note that the first point of a subpath stores the index of the moveTo command. + * + * To use this class create a new instance. Call the command functions such as Path::MoveTo, + * Path::LineTo, Path::CubicTo, etc to append path commands. Then call one of Path::Convert, + * Path::ConvertEvenLines or Path::ConvertWithBackData to generate the polyline approximation. + * Then you can do simplification by calling Path::Simplify or fill a Shape by calling Path::Fill + * on the shape to use features such as Offsetting, Boolean Operations and Tweaking. + * + * Path *path = new Path; + * path->MoveTo(Geom::Point(10, 10)); + * path->LineTo(Geom::Point(100, 10)); + * path->LineTo(Geom::Point(100, 100)); + * path->Close(); + * path->ConvertEvenLines(0.001); // You can use the other variants too + * // insteresting stuff here + * + */ +class Path +{ + friend class Shape; + +public: + + // flags for the path construction + enum + { + descr_ready = 0, + descr_adding_bezier = 1, // we're making a bezier spline, so you can expect pending_bezier_* to have a value + descr_doing_subpath = 2, // we're doing a path, so there is a moveto somewhere + descr_delayed_bezier = 4,// the bezier spline we're doing was initiated by a TempBezierTo(), so we'll need an endpoint + descr_dirty = 16 // the path description was modified + }; + + // some data for the construction: what's pending, and some flags + int descr_flags; + int pending_bezier_cmd; + int pending_bezier_data; + int pending_moveto_cmd; + int pending_moveto_data; + + std::vector<PathDescr*> descr_cmd; /*!< A vector of owned pointers to path commands. */ + + /** + * Points of the polyline approximation. + * + * Since the polyline approximation approximates a Path which can have multiple subpaths, the + * approximation can also have a set of continuous polylines. + */ + struct path_lineto + { + path_lineto(bool m, Geom::Point pp) : isMoveTo(m), p(pp), piece(-1), t(0), closed(false) {} + path_lineto(bool m, Geom::Point pp, int pie, double tt) : isMoveTo(m), p(pp), piece(pie), t(tt), closed(false) {} + + int isMoveTo; /*!< A flag that stores one of polyline_lineto, polyline_moveto, polyline_forced */ + Geom::Point p; /*!< The point itself. */ + int piece; /*!< Index of the path command that created the path segment that this point comes from.*/ + double t; /*!< The time at which this point exists in the path segment. A value between 0 and 1. */ + bool closed; /*!< True indicates that subpath is closed (this point is the last point of a closed subpath) */ + }; + + std::vector<path_lineto> pts; /*!< A vector storing the polyline approximation points. */ + + bool back; /*!< If true, indicates that the polyline approximation is going to have backdata. + No need to set this manually though. When Path::Convert or any of its variants is called, it's set automatically. */ + + Path(); + virtual ~Path(); + + // creation of the path description + + /** + * Clears all stored path commands and resets flags that are used by command functions while adding path + * commands. + */ + void Reset(); + + /** + * Clear all stored path commands, resets flags and imports path commands from the passed Path + * object. + * + * @param who Path object whose path commands to copy. + */ + void Copy (Path * who); + + /** + * Appends a forced point path command. + * + * Forced points are places in the path which are preferred to be kept in the simplification + * algorithm. The simplification algorithm will try to retain those points. This can be beneficial + * in situations such as self-intersections where we would want the intersection point to remain + * unchanged after any simplification is done. + * + * TODO: Confirm this with some testing. + * + * A forced point command can't be appended if there is no active subpath that we are drawing on. + * If you imagine calling these command functions as giving instructions to a pen, a forced point + * command requires that the pen is already touching the canvas. The pen is not on the canvas + * when you instantiate the Path object and it also leaves it when you call Path::Close. The term + * "active subpath" simply means that the pen is already touching the canvas. + * + * @return Index of the path command in the path commands array if it got appended, -1 otherwise. + */ + int ForcePoint(); + + /** + * Appends a close path command. + * + * Close path command can't be appended if there is no acive subpath. + * + * @return Index of the path command in the path commands array if it got appended, -1 otherwise. + */ + int Close(); + + /** Appends a MoveTo path command. + * + * @param ip The point to move to. + * + * @return The index of the path description added. + */ + int MoveTo ( Geom::Point const &ip); + + /** Appends a LineTo path command. + * + * @param ip The point to draw a line to. + * + * @return The index of the path description added. + */ + int LineTo ( Geom::Point const &ip); + + /** + * Appends a CubicBezier path command. + * + * In order to understand the parameters let p0, p1, p2, p3 denote the four points of a + * cubic Bezier curve. p0 is the start point. p3 is the end point. p1 and p2 are the + * two control points. + * + * @param ip The final point of the bezier curve or p3. + * @param iStD 3 * (p1 - p0). Weird way to store it but that's how it is. + * @param iEnD 3 * (p3 - p2). Weird way to store it but that's how it is. + * + * @return The index of the path description added. + */ + int CubicTo ( Geom::Point const &ip, Geom::Point const &iStD, Geom::Point const &iEnD); + + /** + * Appends an ArcTo path command. + * + * The parameters are identical to the SVG elliptical arc command. + * + * @param ip The final point of the arc. + * @param iRx The radius in the x direction. + * @param iRy The radius in the y direction. + * @param angle The angle w.r.t x axis in degrees. TODO: Confirm this + * @param iLargeArc If true, it's the larger arc, if false, it's the smaller one. + * @param iClockwise If true, it's the clockwise arc, if false, it's the anti-clockwise one. + * + * @return The index of the path description added. + */ + int ArcTo ( Geom::Point const &ip, double iRx, double iRy, double angle, bool iLargeArc, bool iClockwise); + + /** + * Adds a control point for the last quadratic bezier spline command. + * + * Adds a control point to the quadratic bezier spline that was last inserted with a call to + * Path::BezierTo. + * + * @param ip The control point. + * + * @return The index of the path description added. + */ + int IntermBezierTo ( Geom::Point const &ip); // add a quadratic bezier spline control point + + /** + * Appends a quadratic bezier spline path command. + * + * A quadratic bezier spline is basically a set of quadratic bezier curves. To simply illustrate + * how this spline is made up, let's define some terms first. Let midpoint(a, b) represent the + * midpoint of the points a and b. Let quad(a, b, c) represent a quadratic Bezier curve with a + * as the start point, b as the control point and c as the end point. + * + * Given a set of points: st, p1, p2, p3, p4, en where st and en are the endpoints and the rest + * are control points, we will have the following quadratic Bezier curves connected end to end. + * + * quad(st, p1, midpoint(p1, p2)) + * quad(midpoint(p1, p2), p2, midpoint(p2, p3)) + * quad(midpoint(p2, p3), p3, midpoint(p3, p4)) + * quad(midpoint(p3, p4), p4, en) + * + * No need to specify the number of control points. That'll be done automatically as you call + * Path::IntermBezierTo to add the control points. The sequence of instructions are like: + * 1. Call Path::BezierTo with the final point. + * 2. Call Path::IntermBezierTo with control points. One call for each control point. + * 3. Call Path::EndBezierTo to mark the end of the quadratic bezier spline command. + * + * Basically, the interface has been designed in such a way that you specify the final point and + * then add control points one by one as many as you like. Once you're done, you call + * Path::EndBezierTo to inform that you're done adding points for the spline. + * + * @param ip The final point of the quadratic bezier spline. + * + * @return The index of the path description added. + */ + int BezierTo ( Geom::Point const &ip); // quadratic bezier spline to this point (control points can be added after this) + + /** + * Finish any ongoing BezierTo instruction. + * + * Once Path::BezierTo has been called, the object expects you to specify control points by + * calling Path::IntermBezierTo for each control point. Once you're done specifying the control + * points you call Path::EndBezierTo to finish the quadratic bezier spline. + * + * @return -1 all the time. + */ + int EndBezierTo(); + + /** + * Appends a quadratic bezier spline path command (without specifying a final point). + * + * If you use Path::BezierTo, you have to specify the final point of the spline first and then + * follow it with all the control points. However, this is kinda counter-intuitive. Visually, we + * would look at the control points first and then the final end point. This function allows a + * similar mechanism. You can start a quadratic bezier spline without mentioning any final point, + * specify as many control points as you like and then while finishing it, you can specify the + * final point of the spline. + * + * The sequence of instructions would be: + * 1. Path::TempBezierTo to start. + * 2. Path::IntermBezierTo to specify control points. One call for each control point. + * 3. Path::EndBezierTo(Geom::Point const&) passing the final point of the quadratic bezier spline and finish the + * quadratic bezier spline command. + * + * @return Index of the description added. + */ + int TempBezierTo(); // start a quadratic bezier spline (control points can be added after this) + + /** + * Finish any ongoing TempBezierTo instruction. + * + * Used to specify the final point of a quadratic bezier spline which was started by calling + * Path::TempBezierTo. + * + * @param ip The final point. + * + * @return -1 all the time. + */ + int EndBezierTo ( Geom::Point const &ip); // ends a quadratic bezier spline (for curves started with TempBezierTo) + + // transforms a description in a polyline (for stroking and filling) + // treshhold is the max length^2 (sort of) + + /** + * Creates a polyline approximation of the path. Doesn't keep any back data. Line segments are + * not split into smaller line segments. + * + * Threshold has no strict definition. It means different things for each path segment. + * + * @param threshhold The error threshold used to approximate curves by line segments. The smaller + * this is, the more line segments there will be. + */ + void Convert (double treshhold); + + /** + * Creates a polyline approximation of the path. Line segments are split into further smaller line segments + * such that each of those line segments is no bigger than threshold. + * + * Breaking up into further smaller line segments is useful for path simplification as you can + * then fit cubic Bezier patches on those small line segments. + * + * Threshold has no strict definition. It means different things for each path segment. + * + * @param threshhold The error threshold used to approximate the path. The smaller this is, the + * more line segments there will be and the better the polyline approximation would be. + */ + void ConvertEvenLines (double treshhold); // decomposes line segments too, for later recomposition + + /** + * Creates a polyline approximation of the path. Line segments are + * not split into smaller line segments. Stores back data for later recomposition. + * + * Threshold has no strict definition. It means different things for each path segment. + * + * @param threshhold The error threshold used to approximate the path. The smaller this is, the + * more line segments there will be and the better the polyline approximation would be. + */ + void ConvertWithBackData (double treshhold); + + // creation of the polyline (you can tinker with these function if you want) + + /** + * Sets the back variable to the value passed in and clears the polyline approximation. + * + * @param nVal True if we are going to have backdata and false otherwise. + */ + void SetBackData (bool nVal); // has back data? + + /** + * Clears the polyline approximation. + */ + void ResetPoints(); // resets to the empty polyline + + /** + * Adds a point to the polyline approximation's list of points. + * + * This is used internally by Path::Convert and its variants, so you'd not need to use it by + * yourself. + * + * If back variable of the instance is set to true, dummy back data will be used with the point. + * Piece being -1 and time being 0. Since this function doesn't take any back data you'll have to + * fill in something. + * + * The point doesn't get added if it's a lineto and the point before it has the same coordinates. + * + * @param iPt The point itself. + * @param mvto If true, it's a moveTo otherwise it's a lineto. + * + * @return Index of the point added if it was added, -1 otherwise. + */ + int AddPoint ( Geom::Point const &iPt, bool mvto = false); // add point + + /** + * Adds a point to the polyline approximation's list of points. Let's you specify back data. + * + * This is used internally by Path::Convert and its variants, so you'd not need to use it by + * yourself. + * + * @param iPt The point itself. + * @param ip The index of the path command that created the segment that this point belongs to. + * @param it The time in that path segment at which this point exists. 0 is beginning and 1 + * is end. + * @param mvto If true, it's a moveTo otherwise it's a lineto. + * + * The point doesn't get added if it's a lineto and the point before it has the same coordinates. + * + * @return Index of the point added if it was added, -1 otherwise. + */ + int AddPoint ( Geom::Point const &iPt, int ip, double it, bool mvto = false); + + /** + * Adds a forced point to the polyline approximation's list of points without specifying any back data. + * + * The argument of this function is useless. The point that gets added as a forced point has the + * same coordinates as the last point that was added. If no points exist or the last one isn't a + * lineto, nothing gets added. + * + * Dummy back data will be used if the back variable of the instance is true. + * + * @param iPt Unused argument. + * + * @return Index of the point added if it was added, -1 otherwise. + */ + int AddForcedPoint ( Geom::Point const &iPt); // add point + + /** + * Add a forced point to the polyline approximation's list of points while specifying backdata. + * + * The argument of this function is useless. The point that gets added as a forced point has the + * same coordinates as the last point that was added. If no points exist or the last one isn't a + * lineto, nothing gets added. The back data is also picked up from the last point that was + * added. + * + * @param iPt Unused argument. + * @param ip Unused argument. + * @param it Unused argument. + * + * @return Index of the point added if it was added, -1 otherwise. + */ + int AddForcedPoint ( Geom::Point const &iPt, int ip, double it); + + /** + * Replace the last point in the polyline approximation's list of points with the passed one. + * + * Nothing gets added if no points exist already. + * + * @param iPt The point to replace the last one with. + * + * @return Index of the last point added if it was added, -1 otherwise. + */ + int ReplacePoint(Geom::Point const &iPt); // replace point + + // transform in a polygon (in a graph, in fact; a subsequent call to ConvertToShape is needed) + // - fills the polyline; justAdd=true doesn't reset the Shape dest, but simply adds the polyline into it + // closeIfNeeded=false prevent the function from closing the path (resulting in a non-eulerian graph + // pathID is a identification number for the path, and is used for recomposing curves from polylines + // give each different Path a different ID, and feed the appropriate orig[] to the ConvertToForme() function + + /** + * Fills the shape with the polyline approximation stored in this object. + * + * For each line segment in the polyline approximation, an edge is created in the shape. + * + * One important point to highlight is the closeIfNeeded argument. For each subpath (where a + * sub path is a moveTo followed by one or more lineTo points) you can either have the start and end + * points being identical or very close (a closed contour) or have them apart (an open contour). + * If you set closeIfNeeded to true, it'll automatically add a closing segment if needed and + * close an open contour by itself. If your contour is already closed, it makes sure that the + * first and last point are the same ones in the graph (instead of being two indentical points). + * If closeIfNeeded is false, it just doesn't care at all. Even if your contour is closed, the + * first and last point will be separate (even though they would be duplicates). + * + * @param dest The shape to fill. + * @param pathID A unique number for this path. The shape will associate this number with each + * edge that comes from this path. Later on, when you use Shape::ConvertToForme you'll pass an array + * of Path objects (named orig) and the shape will use that pathID to do orig[pathID] and get the + * original path information. + * @param justAdd If set to true, this will function will just fill stuff in without resetting + * any existing stuff in Shape. If set to false, it'll make sure to reset the shape and already + * make room for the maximum number of possible points and edges. + * @param closeIfNeeded If set to true, the graph will be closed always. Otherwise, it won't be + * closed. + * @param invert If set to true, the graph is drawn exactly in the manner opposite to the actual + * polyline approximation that this object stores, if false, it's stored indentical to how it's + * in the polyline approximation. + * + * @todo "the graph is drawn exactly in the manner opposite"? Does this mean the edges of the + * directed graph are reversed? + */ + void Fill(Shape *dest, int pathID = -1, bool justAdd = false, + bool closeIfNeeded = true, bool invert = false); + + // - stroke the path; usual parameters: type of cap=butt, type of join=join and miter (see LivarotDefs.h) + // doClose treat the path as closed (ie a loop) + void Stroke(Shape *dest, bool doClose, double width, JoinType join, + ButtType butt, double miter, bool justAdd = false); + + // build a Path that is the outline of the Path instance's description (the result is stored in dest) + // it doesn't compute the exact offset (it's way too complicated, but an approximation made of cubic bezier patches + // and segments. the algorithm was found in a plugin for Impress (by Chris Cox), but i can't find it back... + void Outline(Path *dest, double width, JoinType join, ButtType butt, + double miter); + + // half outline with edges having the same direction as the original + void OutsideOutline(Path *dest, double width, JoinType join, ButtType butt, + double miter); + + // half outline with edges having the opposite direction as the original + void InsideOutline (Path * dest, double width, JoinType join, ButtType butt, + double miter); + + // polyline to cubic bezier patches + + /** + * Simplify the path. + * + * Fit the least possible number of cubic Bezier patches on the polyline approximation while + * respecting the threshold (keeping the error small). The function clears existing path commands + * and the resulting cubic Bezier patches will be pushed as path commands in the instance. + * + * The algorithm to fit cubic Bezier curves on the polyline approximation's points. + * + * http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html + * + * @param threshold The threshold for simplification. A measure of how much error is okay. The + * smaller this number is, the more conservative the fitting algorithm will be. + */ + void Simplify (double treshhold); + + /** + * Simplify the path with a different approach. + * + * This function is also supposed to do simplification but by merging (coalescing) existing + * path descriptions instead of doing any fitting. But I seriously doubt whether this is useful + * at all or works at all. More experimentation needed. TODO + * + * @param threshold The threshold for simplification. + */ + void Coalesce (double tresh); + + // utilities + // piece is a command no in the command list + // "at" is an abcissis on the path portion associated with this command + // 0=beginning of portion, 1=end of portion. + void PointAt (int piece, double at, Geom::Point & pos); + void PointAndTangentAt (int piece, double at, Geom::Point & pos, Geom::Point & tgt); + + // last control point before the command i (i included) + // used when dealing with quadratic bezier spline, cause these can contain arbitrarily many commands + const Geom::Point PrevPoint (const int i) const; + + // dash the polyline + // the result is stored in the polyline, so you lose the original. make a copy before if needed + void DashPolyline(float head,float tail,float body,int nbD, const float dashs[],bool stPlain,float stOffset); + + void DashPolylineFromStyle(SPStyle *style, float scale, float min_len); + + //utilitaire pour inkscape + + /** + * Load a lib2geom Geom::Path in this path object. + * + * The Geom::Path object is read and path commands making it up are appended in the Path object. + * + * @param path The Geom::Path object to load. + * @param tr A transformation matrix. + * @param doTransformation If set to true, the transformation matrix tr is applied on the path + * before it's loaded in this path object. + * @param append If set to true, any existing path commands in this object are retained. If + * set to false, any existing path commands will be cleared. + */ + void LoadPath(Geom::Path const &path, Geom::Affine const &tr, bool doTransformation, bool append = false); + + /** + * Load a lib2geom Geom::PathVector in this path object. (supports transformation) + * + * Any existing path commands in this object are not cleared. + * + * @param pv The Geom::PathVector object to load. + * @param tr A transformation to apply on each path. + * @param doTransformation If set to true, the transformation in tr is applied. + */ + void LoadPathVector(Geom::PathVector const &pv, Geom::Affine const &tr, bool doTransformation); + + /** + * Load a lib2geom Geom::PathVector in this path object. + * + * Any existing path commands in this object are not cleared. + * + * @param pv A reference to the Geom::PathVector object to load. + */ + void LoadPathVector(Geom::PathVector const &pv); + + /** + * Create a lib2geom Geom::PathVector from this Path object. + * + * Looks like the time this was written Geom::PathBuilder didn't exist or maybe + * the author wasn't aware of it. + * + * @return The Geom::PathVector created. + */ + Geom::PathVector MakePathVector(); + + /** + * Apply a transformation on all path commands. + * + * Done by calling the transform method on each path command. + * + * @param trans The transformation to apply. + */ + void Transform(const Geom::Affine &trans); + + // decompose le chemin en ses sous-chemin + // killNoSurf=true -> oublie les chemins de surface nulle + Path** SubPaths(int &outNb,bool killNoSurf); + // pour recuperer les trous + // nbNest= nombre de contours + // conts= debut de chaque contour + // nesting= parent de chaque contour + Path** SubPathsWithNesting(int &outNb,bool killNoSurf,int nbNest,int* nesting,int* conts); + // surface du chemin (considere comme ferme) + double Surface(); + void PolylineBoundingBox(double &l,double &t,double &r,double &b); + void FastBBox(double &l,double &t,double &r,double &b); + // longueur (totale des sous-chemins) + double Length(); + + void ConvertForcedToMoveTo(); + void ConvertForcedToVoid(); + struct cut_position { + int piece; + double t; + }; + cut_position* CurvilignToPosition(int nbCv,double* cvAbs,int &nbCut); + cut_position PointToCurvilignPosition(Geom::Point const &pos, unsigned seg = 0) const; + //Should this take a cut_position as a param? + double PositionToLength(int piece, double t); + + // caution: not tested on quadratic b-splines, most certainly buggy + void ConvertPositionsToMoveTo(int nbPos,cut_position* poss); + void ConvertPositionsToForced(int nbPos,cut_position* poss); + + void Affiche(); + char *svg_dump_path() const; + + bool IsLineSegment(int piece); + + private: + // utilitary functions for the path construction + void CancelBezier (); + void CloseSubpath(); + void InsertMoveTo (Geom::Point const &iPt,int at); + void InsertForcePoint (int at); + void InsertLineTo (Geom::Point const &iPt,int at); + void InsertArcTo (Geom::Point const &ip, double iRx, double iRy, double angle, bool iLargeArc, bool iClockwise,int at); + void InsertCubicTo (Geom::Point const &ip, Geom::Point const &iStD, Geom::Point const &iEnD,int at); + void InsertBezierTo (Geom::Point const &iPt,int iNb,int at); + void InsertIntermBezierTo (Geom::Point const &iPt,int at); + + // creation of dashes: take the polyline given by spP (length spL) and dash it according to head, body, etc. put the result in + // the polyline of this instance + void DashSubPath(int spL, int spP, std::vector<path_lineto> const &orig_pts, float head,float tail,float body,int nbD, const float dashs[],bool stPlain,float stOffset); + + // Functions used by the conversion. + // they append points to the polyline + /** + * The function is quite similar to RecCubicTo. Some of the maths, specially that in + * ArcAnglesAndCenter is too cryptic and I have not spent enough time deriving it yet either. The + * important thing is how the Arc is split into line segments and that I can explain. Given the + * threshold and the two radii, a maximum angle is calculated. This angle is a measure of how big + * a sub-arc you can substitute with a line segment without breaking the threshold. Then you + * divide the whole arc into sectors such that each one's angle is under or equal to maximum + * angle. + * + * @image html livarot-images/arc-threshold.svg + * + * In this image, the red dashed arc is the actual arc that was to be approximated. The blue arcs + * are sectors, each one having an angle equal to or smaller than maximum angle (which is 20 + * degrees) in this example. The final polyline approximation is shown by the pink dotted line + * segments. + * + * TODO: Understand the maths in ArcAnglesAndCenter and how the maximum angle is calculated. + * + */ + void DoArc ( Geom::Point const &iS, Geom::Point const &iE, double rx, double ry, + double angle, bool large, bool wise, double tresh); + /** + * Approximate the passed cubic bezier with line segments. + * + * Basically the function checks if the passed cubic Bezier is "small enough" and if + * it is, it does nothing, if it however isn't "small enough", it splits the cubic Bezier + * curve into two cubic Bezier curves (split at mid point), recursively calls itself on the + * left cubic, add the midpoint to the polyline approximation, call itself on the right + * cubic and done. lev is the maximum recursion depth possible, once it's reached, the function + * returns doing nothing immediately. See the code to understand more about maxL. + * + * The way the algorithm checks if the curve is "small enough" is maths so I'll try to + * explain it here so you can see the equations printed and probably refer it when reading code. + * + * Let \f$\vec{p_{0}}\f$, \f$\vec{p_{1}}\f$, \f$\vec{p_{2}}\f$ and \f$\vec{p_{3}}\f$ be the four + * points that define a cubic Bezier. The first is the start point, last is the end point, + * the two in between are the control points. Given this let me relate these points to the + * arguments that were passed in. + * + * \f[ \vec{iS} = \vec{p_{0}}\f] + * \f[ \vec{iE} = \vec{p_{3}}\f] + * \f[ \vec{iSd} = 3 (\vec{p_{1}} - \vec{p_{0}})\f] + * \f[ \vec{iEd} = 3 (\vec{p_{3}} - \vec{p_{2}})\f] + * + * This is just how livarot represents a Cubic Bezier, nothing I can do about that. The code + * starts by calculating a vector from start point to end point. + * + * \f[ \vec{se} = \vec{iE} - \vec{iS} ]\f + * + * If the length of \f$\vec{se}\f$ is smaller than 0.01, then the cubic bezier's endpoints are + * kinda close, but if the control points are too far away, it can still be a long tall curve, + * so let's see the control points and see how far away they are from the \f$\vec{se}\f$ vector. + * To do that, we measure the lengths of \f$\vec{iSd}\f$ and \f$\vec{iEd}\f$. If both are below + * threshold, we return immediately since it indicates the cubic bezier is "small enough". + * + * if the length is greater than 0.01, we still check the y projections of the control handles + * on the line between start and end points, if these projections are limited by the threshold + * and we didn't mess up the maxL restriction, we are good. + * + * If we ran out of recursion levels, we return anyways. In case this cubic bezier isn't small + * enough, we split it in two parts. There are math equations in the code that do this and I + * spent hours deriving it and they are totally correct. Basically take the usual maths to split + * a cubic Bezier into two parts and just account for the factor of 3 in the control handles + * that livarot adds and you'll end up with correct equations. + * + * TODO: Add derivation here maybe? + * + */ + void RecCubicTo ( Geom::Point const &iS, Geom::Point const &iSd, Geom::Point const &iE, Geom::Point const &iEd, double tresh, int lev, + double maxL = -1.0); + void RecBezierTo ( Geom::Point const &iPt, Geom::Point const &iS, Geom::Point const &iE, double treshhold, int lev, double maxL = -1.0); + + void DoArc ( Geom::Point const &iS, Geom::Point const &iE, double rx, double ry, + double angle, bool large, bool wise, double tresh, int piece); + void RecCubicTo ( Geom::Point const &iS, Geom::Point const &iSd, Geom::Point const &iE, Geom::Point const &iEd, double tresh, int lev, + double st, double et, int piece); + void RecBezierTo ( Geom::Point const &iPt, Geom::Point const &iS, const Geom::Point &iE, double treshhold, int lev, double st, double et, + int piece); + + // don't pay attention + struct offset_orig + { + Path *orig; + int piece; + double tSt, tEn; + double off_dec; + }; + void DoArc ( Geom::Point const &iS, Geom::Point const &iE, double rx, double ry, + double angle, bool large, bool wise, double tresh, int piece, + offset_orig & orig); + void RecCubicTo ( Geom::Point const &iS, Geom::Point const &iSd, Geom::Point const &iE, Geom::Point const &iEd, double tresh, int lev, + double st, double et, int piece, offset_orig & orig); + void RecBezierTo ( Geom::Point const &iPt, Geom::Point const &iS, Geom::Point const &iE, double treshhold, int lev, double st, double et, + int piece, offset_orig & orig); + + static void ArcAngles ( Geom::Point const &iS, Geom::Point const &iE, double rx, + double ry, double angle, bool large, bool wise, + double &sang, double &eang); + static void QuadraticPoint (double t, Geom::Point &oPt, Geom::Point const &iS, Geom::Point const &iM, Geom::Point const &iE); + static void CubicTangent (double t, Geom::Point &oPt, Geom::Point const &iS, + Geom::Point const &iSd, Geom::Point const &iE, + Geom::Point const &iEd); + + struct outline_callback_data + { + Path *orig; + int piece; + double tSt, tEn; + Path *dest; + double x1, y1, x2, y2; + union + { + struct + { + double dx1, dy1, dx2, dy2; + } + c; + struct + { + double mx, my; + } + b; + struct + { + double rx, ry, angle; + bool clock, large; + double stA, enA; + } + a; + } + d; + }; + + typedef void (outlineCallback) (outline_callback_data * data, double tol, double width); + struct outline_callbacks + { + outlineCallback *cubicto; + outlineCallback *bezierto; + outlineCallback *arcto; + }; + + void SubContractOutline (int off, int num_pd, + Path * dest, outline_callbacks & calls, + double tolerance, double width, JoinType join, + ButtType butt, double miter, bool closeIfNeeded, + bool skipMoveto, Geom::Point & lastP, Geom::Point & lastT); + void DoStroke(int off, int N, Shape *dest, bool doClose, double width, JoinType join, + ButtType butt, double miter, bool justAdd = false); + + static void TangentOnSegAt(double at, Geom::Point const &iS, PathDescrLineTo const &fin, + Geom::Point &pos, Geom::Point &tgt, double &len); + static void TangentOnArcAt(double at, Geom::Point const &iS, PathDescrArcTo const &fin, + Geom::Point &pos, Geom::Point &tgt, double &len, double &rad); + static void TangentOnCubAt (double at, Geom::Point const &iS, PathDescrCubicTo const &fin, bool before, + Geom::Point &pos, Geom::Point &tgt, double &len, double &rad); + static void TangentOnBezAt (double at, Geom::Point const &iS, + PathDescrIntermBezierTo & mid, + PathDescrBezierTo & fin, bool before, + Geom::Point & pos, Geom::Point & tgt, double &len, double &rad); + static void OutlineJoin (Path * dest, Geom::Point pos, Geom::Point stNor, Geom::Point enNor, + double width, JoinType join, double miter, int nType); + + static bool IsNulCurve (std::vector<PathDescr*> const &cmd, int curD, Geom::Point const &curX); + + static void RecStdCubicTo (outline_callback_data * data, double tol, + double width, int lev); + static void StdCubicTo (outline_callback_data * data, double tol, + double width); + static void StdBezierTo (outline_callback_data * data, double tol, + double width); + static void RecStdArcTo (outline_callback_data * data, double tol, + double width, int lev); + static void StdArcTo (outline_callback_data * data, double tol, double width); + + + // fonctions annexes pour le stroke + static void DoButt (Shape * dest, double width, ButtType butt, Geom::Point pos, + Geom::Point dir, int &leftNo, int &rightNo); + static void DoJoin (Shape * dest, double width, JoinType join, Geom::Point pos, + Geom::Point prev, Geom::Point next, double miter, double prevL, + double nextL, int *stNo, int *enNo); + static void DoLeftJoin (Shape * dest, double width, JoinType join, Geom::Point pos, + Geom::Point prev, Geom::Point next, double miter, double prevL, + double nextL, int &leftStNo, int &leftEnNo,int pathID=-1,int pieceID=0,double tID=0.0); + static void DoRightJoin (Shape * dest, double width, JoinType join, Geom::Point pos, + Geom::Point prev, Geom::Point next, double miter, double prevL, + double nextL, int &rightStNo, int &rightEnNo,int pathID=-1,int pieceID=0,double tID=0.0); + static void RecRound (Shape * dest, int sNo, int eNo, + Geom::Point const &iS, Geom::Point const &iE, + Geom::Point const &nS, Geom::Point const &nE, + Geom::Point &origine,float width); + + + /** + * Simpilfy a sequence of points. + * + * Fit cubic Bezier patches on the sequence of points defined by the passed parameters. This + * sequence is just a subset of the polyline approximation points stored in the Path object. + * + * @param off The offset to the first point to process. + * @param N The total number of points in the sequence. + * @param threshhold The threshold to respect during simplification. The higher this number is, + * the more relaxed you're making the simplifier. The smaller the number, the more strict you're + * making the simplifier. + */ + void DoSimplify(int off, int N, double treshhold); + + /** + * Fit a cubic Bezier patch on the sequence of points. + * + * @param off The index of the first point of the sequence to fit on. + * @param N The total number of points you want to fit on. + * @param res Reference to the Cubic Bezier description where the resulting control points will + * be stored. + * @param worstP Reference to a point index. This will be changed to whichever point measures the + * highest deviation from the fitted curve. + * + * @return True if the fit respected threshold, false otherwise. + */ + bool AttemptSimplify(int off, int N, double treshhold, PathDescrCubicTo &res, int &worstP); + /* + * The actual fitting code that takes a sequence and fits stuff on it. + * + * Totally based on the algorithm from: + * http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html + * + * @param start The start point of the cubic bezier which is already known. + * @param res The cubic Bezier path command that function will populate after doing the maths. + * @param Xk The array of X coordinates of the point to fit. + * @param Yk The array of Y coordinates of the point to fit. + * @param Qk An array to store some intermediate values. + * @param tk The time values for the points. + * @param nbPt The total points to fit on. + * + * @return True if the fit was done correctly, false if something bad happened. (Non-invertible + * matrix). + */ + static bool FitCubic(Geom::Point const &start, + PathDescrCubicTo &res, + double *Xk, double *Yk, double *Qk, double *tk, int nbPt); + /** + * Structure to keep some data for fitting. + * + * Note that the pointers in the structure are going to store arrays. The comments explain what + * each element of a particular array stores. Also note that the length mentioned in the comment + * for tk and lk is not the straight line distance but the length as measured by walking on the + * line segments connecting the points. + */ + struct fitting_tables { + int nbPt; /*!< The points to fit on in a particular iteration */ + int maxPt; /*!< Maximum number of points these arrays here can store */ + int inPt; /*!< Total points whose X, Y, lk are all populated here */ + double *Xk; /*!< X coordinate of the point */ + double *Yk; /*!< Y coordinate of the point */ + double *Qk; /*!< A special value needed by the fitting algorithm */ + double *tk; /*!< A number between 0 and 1 that is the fraction (length b/w first point to this point along the line segments)/(total length) */ + double *lk; /*!< Length of the line segment from the previous point to this point */ + char *fk; /*!< A flag if 0x01 indicates forced point and if 0x00 indicates a normal point */ + double totLen; /*!< Total length of the polyline or you can say the sum of lengths of all line segments. */ + }; + + /** + * Fit Cubic Bezier patch using the fitting table data. + * + * @param data The fitting_tables data needed for fitting. ExtendFit sets that up for this + * function. + * @param threshhold The threshold to respect. + * @param res The cubic Bezier command which this function will populate. + * @param worstP The point with the worst error. + */ + bool AttemptSimplify (fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP); + + /** + * Fit Cubic Bezier patch on the points. + * + * This uses data already calculated by probably the same function if it exists. + * The data that's reused is apparently the X, Y and lk values. However, I think there is a + * problem with this caching mechanism. See the inline comments of ExtendFit. + * + * This function prepares data in fitting tables and calls the AttemptSimplify version that takes + * fitting_tables data. + * + * @param off The offset to the first point. + * @param N The total number of points in that sequence. + * @param data The data structure which keeps data saved for later use by the same function. + * @param threshhold The threshold to respect. + * @param res cubic Bezier path command where this function will store the control point handles. + * @param worstP Function will set this to the point with the worst error. + * + * @return True if the threshold was respected, otherwise false. + */ + bool ExtendFit(int off, int N, fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP); + /** + * Peform an iteration of Newton-Raphson to improve t values. + * + * TODO: Place derivation here with embedded latex maybe. + */ + double RaffineTk (Geom::Point pt, Geom::Point p0, Geom::Point p1, Geom::Point p2, Geom::Point p3, double it); + void FlushPendingAddition(Path* dest,PathDescr *lastAddition,PathDescrCubicTo &lastCubic,int lastAD); + +private: + /** + * Add a Geom::Curve's equivalent path description. + * + * Any straight curve (line or otherwise that's straight) is added as line. CubicBezier + * and EllipticalArcs are handled manually, while any other Geom::Curve type is handled by + * converting to cubic beziers using Geom::cubicbezierpath_from_sbasis and recursively calling + * the same function. + * + * There is one special reason for using is_straight_curve to figure out if a CubicBezier is + * actually a line and making sure that it is added as a line not as a straight line CubicBezier + * (a CubicBezier with control points being the same as end points). Sometimes when you're drawing + * straight line segments with the Bezier (pen) tool, Inkscape would place a straight CubicBezier + * instead of a line segment. The call to Path::Convert or Path::ConvertWithBackData would break + * up this line segment into smaller line segments which is not what we want (we want it to break + * only real curves) not curves that are actually just straight lines. + * + * @param c The Geom::Curve whose path description to create/add. + */ + void AddCurve(Geom::Curve const &c); + +}; +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/PathConversion.cpp b/src/livarot/PathConversion.cpp new file mode 100644 index 0000000..81dc32d --- /dev/null +++ b/src/livarot/PathConversion.cpp @@ -0,0 +1,1638 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glib.h> +#include <2geom/transforms.h> +#include "Path.h" +#include "Shape.h" +#include "livarot/path-description.h" + +/* + * path description -> polyline + * and Path -> Shape (the Fill() function at the bottom) + * nathing fancy here: take each command and append an approximation of it to the polyline + */ + +void Path::ConvertWithBackData(double treshhold) +{ + // if a quadratic Bezier spline was being added (Path::BezierTo or Path::TempBezierTo + // were called and Path::EndBezierTo hasn't been called yet), cancel it + if ( descr_flags & descr_adding_bezier ) { + CancelBezier(); + } + + // are we doing a sub path? if yes, clear the flags. CloseSubPath just clears the flags + // it doesn't close a sub path + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } + + // set the backdata flag to true since this function will be calculating and storing backdata stuff + SetBackData(true); + // clears any pre-existing polyline approximation stuff + ResetPoints(); + + // nothing to approximate so return + if ( descr_cmd.empty() ) { + return; + } + + Geom::Point curX; // the last point added + // the description to process. We start with 1 usually since the first is always a MoveTo that + // we handle before the loop (see below). In the case that the first is not a moveTo, We set + // curP to 0. + int curP = 1; + // index of the last moveto point. Useful when a close path command is encountered. + int lastMoveTo = -1; + + // The initial moveto. + // if the first command is a moveTo, set that as the lastPoint (curX) otherwise add a point at + // the origin as a moveTo. + { + int const firstTyp = descr_cmd[0]->getType(); + if ( firstTyp == descr_moveto ) { + curX = dynamic_cast<PathDescrMoveTo *>(descr_cmd[0])->p; + } else { + curP = 0; + curX[Geom::X] = curX[Geom::Y] = 0; + } + // tiny detail to see here is that piece (the index of the path command this point comes from) is set to 0 which + // may or may not be true. If there was not a MoveTo, index 0 can have other description types. + lastMoveTo = AddPoint(curX, 0, 0.0, true); + } + + // And the rest, one by one. + // within this loop, curP holds the current path command index, curX holds the last point added + while ( curP < int(descr_cmd.size()) ) { + + int const nType = descr_cmd[curP]->getType(); + Geom::Point nextX; + + switch (nType) { + case descr_forced: { + // just add a forced point (at the last point added). These arguments are useless by the way. + AddForcedPoint(curX, curP, 1.0); + curP++; + break; + } + + case descr_moveto: { + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo*>(descr_cmd[curP]); + nextX = nData->p; + // add the moveTo point and also store this in lastMoveTo + lastMoveTo = AddPoint(nextX, curP, 0.0, true); + // et on avance + curP++; + break; + } + + case descr_close: { + // add the lastMoveTo point again + nextX = pts[lastMoveTo].p; + int n = AddPoint(nextX, curP, 1.0, false); + // we check if n > 0 because in some cases the last point has already been added so AddPoint would + // return -1 .. but then that last point won't get marked with closed = true .. I wonder if that would cause + // problems. Just to explain this say: + // MoveTo(0, 0); LineTo(10, 0); LineTo(10, 10); LineTo(0, 10); LineTo(0, 0); Close(); + // LineTo(0, 0) would have already added a point at origin. So AddPoint won't add anything. But my point is that + // then the last point (0,0) won't get marked as closed = true which it should be. + // But then maybe this doesn't matter because closed variable is barely used. + if (n > 0) pts[n].closed = true; + curP++; + break; + } + + case descr_lineto: { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[curP]); + nextX = nData->p; + AddPoint(nextX,curP,1.0,false); + // et on avance + curP++; + break; + } + + case descr_cubicto: { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[curP]); + nextX = nData->p; + // RecCubicTo will see if threshold is fine with approximating this cubic bezier with + // a line segment through the start and end points. If no, it'd split the cubic at its + // center point and recursively call itself on the left and right side. The center point + // gets added in the points list too. + RecCubicTo(curX, nData->start, nextX, nData->end, treshhold, 8, 0.0, 1.0, curP); + // RecCubicTo adds any points inside the cubic and last one is added here + AddPoint(nextX, curP, 1.0, false); + // et on avance + curP++; + break; + } + + case descr_arcto: { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[curP]); + nextX = nData->p; + // Similar to RecCubicTo, just for Arcs + DoArc(curX, nextX, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise, treshhold, curP); + AddPoint(nextX, curP, 1.0, false); + // et on avance + curP++; + break; + } + + case descr_bezierto: { + PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[curP]); + int nbInterm = nBData->nb; + nextX = nBData->p; + // same as RecCubicTo and RecArcTo but for quadratic bezier splines + + int ip = curP + 1; + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]); + + if ( nbInterm >= 1 ) { + Geom::Point bx = curX; + Geom::Point dx = nData->p; + Geom::Point cx = 2 * bx - dx; + + ip++; + nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]); + + for (int k = 0; k < nbInterm - 1; k++) { + bx = cx; + cx = dx; + + dx = nData->p; + ip++; + nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]); + + Geom::Point stx; + stx = (bx + cx) / 2; + if ( k > 0 ) { + AddPoint(stx,curP - 1+k,1.0,false); + } + + { + Geom::Point mx; + mx = (cx + dx) / 2; + RecBezierTo(cx, stx, mx, treshhold, 8, 0.0, 1.0, curP + k); + } + } + { + bx = cx; + cx = dx; + + dx = nextX; + dx = 2 * dx - cx; + + Geom::Point stx; + stx = (bx + cx) / 2; + + if ( nbInterm > 1 ) { + AddPoint(stx, curP + nbInterm - 2, 1.0, false); + } + + { + Geom::Point mx; + mx = (cx + dx) / 2; + RecBezierTo(cx, stx, mx, treshhold, 8, 0.0, 1.0, curP + nbInterm - 1); + } + } + + } + + + AddPoint(nextX, curP - 1 + nbInterm, 1.0, false); + + // et on avance + curP += 1 + nbInterm; + break; + } + } + curX = nextX; + } +} + + +void Path::Convert(double treshhold) +{ + if ( descr_flags & descr_adding_bezier ) { + CancelBezier(); + } + + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } + + SetBackData(false); + ResetPoints(); + if ( descr_cmd.empty() ) { + return; + } + + Geom::Point curX; + int curP = 1; + int lastMoveTo = 0; + + // le moveto + { + int const firstTyp = descr_cmd[0]->getType(); + if ( firstTyp == descr_moveto ) { + curX = dynamic_cast<PathDescrMoveTo *>(descr_cmd[0])->p; + } else { + curP = 0; + curX[0] = curX[1] = 0; + } + lastMoveTo = AddPoint(curX, true); + } + descr_cmd[0]->associated = lastMoveTo; + + // et le reste, 1 par 1 + while ( curP < int(descr_cmd.size()) ) { + + int const nType = descr_cmd[curP]->getType(); + Geom::Point nextX; + + switch (nType) { + case descr_forced: { + descr_cmd[curP]->associated = AddForcedPoint(curX); + curP++; + break; + } + + case descr_moveto: { + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]); + nextX = nData->p; + lastMoveTo = AddPoint(nextX, true); + descr_cmd[curP]->associated = lastMoveTo; + + // et on avance + curP++; + break; + } + + case descr_close: { + nextX = pts[lastMoveTo].p; + descr_cmd[curP]->associated = AddPoint(nextX, false); + if ( descr_cmd[curP]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curP]->associated = 0; + } else { + descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated; + } + } + if ( descr_cmd[curP]->associated > 0 ) { + pts[descr_cmd[curP]->associated].closed = true; + } + curP++; + break; + } + + case descr_lineto: { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[curP]); + nextX = nData->p; + descr_cmd[curP]->associated = AddPoint(nextX, false); + if ( descr_cmd[curP]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curP]->associated = 0; + } else { + descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated; + } + } + // et on avance + curP++; + break; + } + + case descr_cubicto: { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[curP]); + nextX = nData->p; + RecCubicTo(curX, nData->start, nextX, nData->end, treshhold, 8); + descr_cmd[curP]->associated = AddPoint(nextX,false); + if ( descr_cmd[curP]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curP]->associated = 0; + } else { + descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated; + } + } + // et on avance + curP++; + break; + } + + case descr_arcto: { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[curP]); + nextX = nData->p; + DoArc(curX, nextX, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise, treshhold); + descr_cmd[curP]->associated = AddPoint(nextX, false); + if ( descr_cmd[curP]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curP]->associated = 0; + } else { + descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated; + } + } + // et on avance + curP++; + break; + } + + case descr_bezierto: { + PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[curP]); + int nbInterm = nBData->nb; + nextX = nBData->p; + int curBD = curP; + + curP++; + int ip = curP; + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]); + + if ( nbInterm == 1 ) { + Geom::Point const midX = nData->p; + RecBezierTo(midX, curX, nextX, treshhold, 8); + } else if ( nbInterm > 1 ) { + Geom::Point bx = curX; + Geom::Point dx = nData->p; + Geom::Point cx = 2 * bx - dx; + + ip++; + nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]); + + for (int k = 0; k < nbInterm - 1; k++) { + bx = cx; + cx = dx; + + dx = nData->p; + ip++; + nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]); + + Geom::Point stx = (bx + cx) / 2; + if ( k > 0 ) { + descr_cmd[ip - 2]->associated = AddPoint(stx, false); + if ( descr_cmd[ip - 2]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[ip - 2]->associated = 0; + } else { + descr_cmd[ip - 2]->associated = descr_cmd[ip - 3]->associated; + } + } + } + + { + Geom::Point const mx = (cx + dx) / 2; + RecBezierTo(cx, stx, mx, treshhold, 8); + } + } + + { + bx = cx; + cx = dx; + + dx = nextX; + dx = 2 * dx - cx; + + Geom::Point stx = (bx + cx) / 2; + + descr_cmd[ip - 1]->associated = AddPoint(stx, false); + if ( descr_cmd[ip - 1]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[ip - 1]->associated = 0; + } else { + descr_cmd[ip - 1]->associated = descr_cmd[ip - 2]->associated; + } + } + + { + Geom::Point mx = (cx + dx) / 2; + RecBezierTo(cx, stx, mx, treshhold, 8); + } + } + } + + descr_cmd[curBD]->associated = AddPoint(nextX, false); + if ( descr_cmd[curBD]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curBD]->associated = 0; + } else { + descr_cmd[curBD]->associated = descr_cmd[curBD - 1]->associated; + } + } + + // et on avance + curP += nbInterm; + break; + } + } + + curX = nextX; + } +} + +void Path::ConvertEvenLines(double treshhold) +{ + if ( descr_flags & descr_adding_bezier ) { + CancelBezier(); + } + + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } + + SetBackData(false); + ResetPoints(); + if ( descr_cmd.empty() ) { + return; + } + + Geom::Point curX; + int curP = 1; + int lastMoveTo = 0; + + // le moveto + { + int const firstTyp = descr_cmd[0]->getType(); + if ( firstTyp == descr_moveto ) { + curX = dynamic_cast<PathDescrMoveTo *>(descr_cmd[0])->p; + } else { + curP = 0; + curX[0] = curX[1] = 0; + } + lastMoveTo = AddPoint(curX, true); + } + descr_cmd[0]->associated = lastMoveTo; + + // et le reste, 1 par 1 + while ( curP < int(descr_cmd.size()) ) { + + int const nType = descr_cmd[curP]->getType(); + Geom::Point nextX; + + switch (nType) { + case descr_forced: { + descr_cmd[curP]->associated = AddForcedPoint(curX); + curP++; + break; + } + + case descr_moveto: { + PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]); + nextX = nData->p; + lastMoveTo = AddPoint(nextX,true); + descr_cmd[curP]->associated = lastMoveTo; + + // et on avance + curP++; + break; + } + + case descr_close: { + nextX = pts[lastMoveTo].p; + { + Geom::Point nexcur; + nexcur = nextX - curX; + const double segL = Geom::L2(nexcur); + if ( (segL > treshhold) && (treshhold > 0) ) { + for (double i = treshhold; i < segL; i += treshhold) { + Geom::Point nX; + nX = (segL - i) * curX + i * nextX; + nX /= segL; + AddPoint(nX); + } + } + } + + descr_cmd[curP]->associated = AddPoint(nextX,false); + if ( descr_cmd[curP]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curP]->associated = 0; + } else { + descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated; + } + } + if ( descr_cmd[curP]->associated > 0 ) { + pts[descr_cmd[curP]->associated].closed = true; + } + curP++; + break; + } + + case descr_lineto: { + PathDescrLineTo* nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[curP]); + nextX = nData->p; + Geom::Point nexcur = nextX - curX; + const double segL = L2(nexcur); + if ( (segL > treshhold) && (treshhold > 0)) { + for (double i = treshhold; i < segL; i += treshhold) { + Geom::Point nX = ((segL - i) * curX + i * nextX) / segL; + AddPoint(nX); + } + } + + descr_cmd[curP]->associated = AddPoint(nextX,false); + if ( descr_cmd[curP]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curP]->associated = 0; + } else { + descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated; + } + } + // et on avance + curP++; + break; + } + + case descr_cubicto: { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[curP]); + nextX = nData->p; + RecCubicTo(curX, nData->start, nextX, nData->end, treshhold, 8, 4 * treshhold); + descr_cmd[curP]->associated = AddPoint(nextX, false); + if ( descr_cmd[curP]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curP]->associated = 0; + } else { + descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated; + } + } + // et on avance + curP++; + break; + } + + case descr_arcto: { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[curP]); + nextX = nData->p; + DoArc(curX, nextX, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise, treshhold); + descr_cmd[curP]->associated =AddPoint(nextX, false); + if ( descr_cmd[curP]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curP]->associated = 0; + } else { + descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated; + } + } + + // et on avance + curP++; + break; + } + + case descr_bezierto: { + PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[curP]); + int nbInterm = nBData->nb; + nextX = nBData->p; + int curBD = curP; + + curP++; + int ip = curP; + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]); + + if ( nbInterm == 1 ) { + Geom::Point const midX = nData->p; + RecBezierTo(midX, curX, nextX, treshhold, 8, 4 * treshhold); + } else if ( nbInterm > 1 ) { + Geom::Point bx = curX; + Geom::Point dx = nData->p; + Geom::Point cx = 2 * bx - dx; + + ip++; + nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]); + + for (int k = 0; k < nbInterm - 1; k++) { + bx = cx; + cx = dx; + dx = nData->p; + + ip++; + nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]); + + Geom::Point stx = (bx+cx) / 2; + if ( k > 0 ) { + descr_cmd[ip - 2]->associated = AddPoint(stx, false); + if ( descr_cmd[ip - 2]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[ip- 2]->associated = 0; + } else { + descr_cmd[ip - 2]->associated = descr_cmd[ip - 3]->associated; + } + } + } + + { + Geom::Point const mx = (cx + dx) / 2; + RecBezierTo(cx, stx, mx, treshhold, 8, 4 * treshhold); + } + } + + { + bx = cx; + cx = dx; + + dx = nextX; + dx = 2 * dx - cx; + + Geom::Point const stx = (bx + cx) / 2; + + descr_cmd[ip - 1]->associated = AddPoint(stx, false); + if ( descr_cmd[ip - 1]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[ip - 1]->associated = 0; + } else { + descr_cmd[ip - 1]->associated = descr_cmd[ip - 2]->associated; + } + } + + { + Geom::Point const mx = (cx + dx) / 2; + RecBezierTo(cx, stx, mx, treshhold, 8, 4 * treshhold); + } + } + } + + descr_cmd[curBD]->associated = AddPoint(nextX, false); + if ( descr_cmd[curBD]->associated < 0 ) { + if ( curP == 0 ) { + descr_cmd[curBD]->associated = 0; + } else { + descr_cmd[curBD]->associated = descr_cmd[curBD - 1]->associated; + } + } + + // et on avance + curP += nbInterm; + break; + } + } + if ( Geom::LInfty(curX - nextX) > 0.00001 ) { + curX = nextX; + } + } +} + +const Geom::Point Path::PrevPoint(int i) const +{ + /* TODO: I suspect this should assert `(unsigned) i < descr_nb'. We can probably change + the argument to unsigned. descr_nb should probably be changed to unsigned too. */ + g_assert( i >= 0 ); + switch ( descr_cmd[i]->getType() ) { + case descr_moveto: { + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]); + return nData->p; + } + case descr_lineto: { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]); + return nData->p; + } + case descr_arcto: { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]); + return nData->p; + } + case descr_cubicto: { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]); + return nData->p; + } + case descr_bezierto: { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]); + return nData->p; + } + case descr_interm_bezier: + case descr_close: + case descr_forced: + return PrevPoint(i - 1); + default: + g_assert_not_reached(); + return Geom::Point(0, 0); + } +} + +// utilitaries: given a quadratic bezier curve (start point, control point, end point, ie that's a clamped curve), +// and an abcissis on it, get the point with that abcissis. +// warning: it's NOT a curvilign abcissis (or whatever you call that in english), so "t" is NOT the length of "start point"->"result point" +void Path::QuadraticPoint(double t, Geom::Point &oPt, + const Geom::Point &iS, const Geom::Point &iM, const Geom::Point &iE) +{ + Geom::Point const ax = iE - 2 * iM + iS; + Geom::Point const bx = 2 * iM - 2 * iS; + Geom::Point const cx = iS; + + oPt = t * t * ax + t * bx + cx; +} +// idem for cubic bezier patch +void Path::CubicTangent(double t, Geom::Point &oPt, const Geom::Point &iS, const Geom::Point &isD, + const Geom::Point &iE, const Geom::Point &ieD) +{ + Geom::Point const ax = ieD - 2 * iE + 2 * iS + isD; + Geom::Point const bx = 3 * iE - ieD - 2 * isD - 3 * iS; + Geom::Point const cx = isD; + + oPt = 3 * t * t * ax + 2 * t * bx + cx; +} + +// extract interesting info of a SVG arc description +static void ArcAnglesAndCenter(Geom::Point const &iS, Geom::Point const &iE, + double rx, double ry, double angle, + bool large, bool wise, + double &sang, double &eang, Geom::Point &dr); + +void Path::ArcAngles(const Geom::Point &iS, const Geom::Point &iE, + double rx, double ry, double angle, bool large, bool wise, double &sang, double &eang) +{ + Geom::Point dr; + ArcAnglesAndCenter(iS, iE, rx, ry, angle, large, wise, sang, eang, dr); +} + +/* N.B. If iS == iE then sang,eang,dr each become NaN. Probably a bug. */ +static void ArcAnglesAndCenter(Geom::Point const &iS, Geom::Point const &iE, + double rx, double ry, double angle, + bool large, bool wise, + double &sang, double &eang, Geom::Point &dr) +{ + Geom::Point se = iE - iS; + Geom::Point ca(cos(angle), sin(angle)); + Geom::Point cse(dot(ca, se), cross(ca, se)); + cse[0] /= rx; + cse[1] /= ry; + double const lensq = dot(cse,cse); + Geom::Point csd = ( ( lensq < 4 + ? sqrt( 1/lensq - .25 ) + : 0.0 ) + * cse.ccw() ); + + Geom::Point ra = -csd - 0.5 * cse; + if ( ra[0] <= -1 ) { + sang = M_PI; + } else if ( ra[0] >= 1 ) { + sang = 0; + } else { + sang = acos(ra[0]); + if ( ra[1] < 0 ) { + sang = 2 * M_PI - sang; + } + } + + ra = -csd + 0.5 * cse; + if ( ra[0] <= -1 ) { + eang = M_PI; + } else if ( ra[0] >= 1 ) { + eang = 0; + } else { + eang = acos(ra[0]); + if ( ra[1] < 0 ) { + eang = 2 * M_PI - eang; + } + } + + csd[0] *= rx; + csd[1] *= ry; + ca[1] = -ca[1]; // because it's the inverse rotation + + dr[0] = dot(ca, csd); + dr[1] = cross(ca, csd); + + ca[1] = -ca[1]; + + if ( wise ) { + + if (large) { + dr = -dr; + double swap = eang; + eang = sang; + sang = swap; + eang += M_PI; + sang += M_PI; + if ( eang >= 2*M_PI ) { + eang -= 2*M_PI; + } + if ( sang >= 2*M_PI ) { + sang -= 2*M_PI; + } + } + + } else { + if (!large) { + dr = -dr; + double swap = eang; + eang = sang; + sang = swap; + eang += M_PI; + sang += M_PI; + if ( eang >= 2*M_PI ) { + eang -= 2 * M_PI; + } + if ( sang >= 2*M_PI ) { + sang -= 2 * M_PI; + } + } + } + + dr += 0.5 * (iS + iE); +} + + + +void Path::DoArc(Geom::Point const &iS, Geom::Point const &iE, + double const rx, double const ry, double const angle, + bool const large, bool const wise, double const tresh) +{ + /* TODO: Check that our behaviour is standards-conformant if iS and iE are (much) further + apart than the diameter. Also check that we do the right thing for negative radius. + (Same for the other DoArc functions in this file.) */ + if ( rx <= 0.0001 || ry <= 0.0001 || tresh <= 1e-8) { + return; + // We always add a lineto afterwards, so this is fine. + // [on ajoute toujours un lineto apres, donc c bon] + } + + double sang; + double eang; + Geom::Point dr_temp; + ArcAnglesAndCenter(iS, iE, rx, ry, angle*M_PI/180.0, large, wise, sang, eang, dr_temp); + Geom::Point dr = dr_temp; + /* TODO: This isn't as good numerically as treating iS and iE as primary. E.g. consider + the case of low curvature (i.e. very large radius). */ + + Geom::Scale const ar(rx, ry); + Geom::Rotate cb(sang); + Geom::Rotate cbangle(angle*M_PI/180.0); + double max_ang = 2 * acos ( 1 - tresh / (fmax(rx, ry) ) ); + max_ang = fmin (max_ang, M_PI / 2 ); + int const num_sectors = abs(sang - eang) / max_ang + 1; + + if (wise) { + + + if ( sang < eang ) { + sang += 2*M_PI; + } + double const incr = (eang - sang) / num_sectors; + Geom::Rotate const omega(incr); + for (double b = sang + incr ; b > eang ; b += incr) { + cb = omega * cb; + AddPoint( cb.vector() * ar * cbangle + dr ); + } + + } else { + + if ( sang > eang ) { + sang -= 2*M_PI; + } + double const incr = (eang - sang) / num_sectors; + Geom::Rotate const omega(incr); + for (double b = sang + incr ; b < eang ; b += incr) { + cb = omega * cb; + AddPoint( cb.vector() * ar * cbangle + dr); + } + } +} + + +void Path::RecCubicTo( Geom::Point const &iS, Geom::Point const &isD, + Geom::Point const &iE, Geom::Point const &ieD, + double tresh, int lev, double maxL) +{ + // vector from start to end point + Geom::Point se = iE - iS; + // length of that vector + const double dC = Geom::L2(se); + // if the vector from start to end point is smaller than 0.01 + if ( dC < 0.01 ) { + // we still need to get an idea of how far away the curve goes from the start to end line segment se + // for that, we measure lengths of isD and ieD + const double sC = dot(isD,isD); + const double eC = dot(ieD,ieD); + // if they are limited by tresh, great + if ( sC < tresh && eC < tresh ) { + return; + } + // otherwise proceed + + } else { + // okay so length is greater than or equal to 0.01, we can still check the perpendicular component + // of the control handles and see if they are limited by tresh + const double sC = fabs(cross(se, isD)) / dC; + const double eC = fabs(cross(se, ieD)) / dC; + if ( sC < tresh && eC < tresh ) { + // presque tt droit -> attention si on nous demande de bien subdiviser les petits segments + // if the perpendicular is limited and a maxL is set, check if maxL is being respected, if yes + // return otherwise we split + if ( maxL > 0 && dC > maxL ) { + if ( lev <= 0 ) { + return; + } + // maths for splitting one cubic bezier into two + Geom::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD); + Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + + Geom::Point hisD = 0.5 * isD; + Geom::Point hieD = 0.5 * ieD; + + RecCubicTo(iS, hisD, m, md, tresh, lev - 1, maxL); + AddPoint(m); + RecCubicTo(m, md, iE, hieD, tresh, lev - 1,maxL); + } + return; + } + } + + if ( lev <= 0 ) { + return; + } + + { + Geom::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD); + Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + + Geom::Point hisD = 0.5 * isD; + Geom::Point hieD = 0.5 * ieD; + + RecCubicTo(iS, hisD, m, md, tresh, lev - 1, maxL); + AddPoint(m); + RecCubicTo(m, md, iE, hieD, tresh, lev - 1,maxL); + } +} + + + +void Path::RecBezierTo(const Geom::Point &iP, + const Geom::Point &iS, + const Geom::Point &iE, + double tresh, int lev, double maxL) +{ + if ( lev <= 0 ) { + return; + } + + Geom::Point ps = iS - iP; + Geom::Point pe = iE - iP; + Geom::Point se = iE - iS; + double s = fabs(cross(pe, ps)); + if ( s < tresh ) { + const double l = L2(se); + if ( maxL > 0 && l > maxL ) { + const Geom::Point m = 0.25 * (iS + iE + 2 * iP); + Geom::Point md = 0.5 * (iS + iP); + RecBezierTo(md, iS, m, tresh, lev - 1, maxL); + AddPoint(m); + md = 0.5 * (iP + iE); + RecBezierTo(md, m, iE, tresh, lev - 1, maxL); + } + return; + } + + { + const Geom::Point m = 0.25 * (iS + iE + 2 * iP); + Geom::Point md = 0.5 * (iS + iP); + RecBezierTo(md, iS, m, tresh, lev - 1, maxL); + AddPoint(m); + md = 0.5 * (iP + iE); + RecBezierTo(md, m, iE, tresh, lev - 1, maxL); + } +} + + +void Path::DoArc(Geom::Point const &iS, Geom::Point const &iE, + double const rx, double const ry, double const angle, + bool const large, bool const wise, double const tresh, int const piece) +{ + /* TODO: Check that our behaviour is standards-conformant if iS and iE are (much) further + apart than the diameter. Also check that we do the right thing for negative radius. + (Same for the other DoArc functions in this file.) */ + if ( rx <= 0.0001 || ry <= 0.0001 || tresh <= 1e-8 ) { + return; + // We always add a lineto afterwards, so this is fine. + // [on ajoute toujours un lineto apres, donc c bon] + } + + double sang; + double eang; + Geom::Point dr_temp; + ArcAnglesAndCenter(iS, iE, rx, ry, angle*M_PI/180.0, large, wise, sang, eang, dr_temp); + Geom::Point dr = dr_temp; + /* TODO: This isn't as good numerically as treating iS and iE as primary. E.g. consider + the case of low curvature (i.e. very large radius). */ + + Geom::Scale const ar(rx, ry); + Geom::Rotate cb(sang); + Geom::Rotate cbangle(angle*M_PI/180.0); + + // max angle is basically the maximum arc angle you can have that won't create + // an arc that exceeds the threshold + double max_ang = 2 * acos ( 1 - tresh / fmax(rx, ry) ); + max_ang = fmin (max_ang, M_PI / 2 ); + // divide the whole arc range into sectors such that each sector + // is no bigger than max ang + int const num_sectors = abs(sang - eang) / max_ang + 1; + + if (wise) { + if ( sang < eang ) { + sang += 2*M_PI; + } + double const incr = (eang - sang) / num_sectors; + Geom::Rotate const omega(incr); + for (double b = sang + incr; b > eang; b += incr) { + cb = omega * cb; + AddPoint(cb.vector() * ar * cbangle + dr, piece, (sang - b) / (sang - eang)); + } + + } else { + + if ( sang > eang ) { + sang -= 2 * M_PI; + } + double const incr = (eang - sang) / num_sectors; + Geom::Rotate const omega(incr); + for (double b = sang + incr ; b < eang ; b += incr) { + cb = omega * cb; + AddPoint(cb.vector() * ar * cbangle + dr, piece, (b - sang) / (eang - sang)); + } + } +} + +void Path::RecCubicTo(Geom::Point const &iS, Geom::Point const &isD, + Geom::Point const &iE, Geom::Point const &ieD, + double tresh, int lev, double st, double et, int piece) +{ + const Geom::Point se = iE - iS; + const double dC = Geom::L2(se); + if ( dC < 0.01 ) { + const double sC = dot(isD, isD); + const double eC = dot(ieD, ieD); + if ( sC < tresh && eC < tresh ) { + return; + } + } else { + const double sC = fabs(cross(se, isD)) / dC; + const double eC = fabs(cross(se, ieD)) / dC; + if ( sC < tresh && eC < tresh ) { + return; + } + } + + if ( lev <= 0 ) { + return; + } + + Geom::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD); + Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + double mt = (st + et) / 2; + + Geom::Point hisD = 0.5 * isD; + Geom::Point hieD = 0.5 * ieD; + + RecCubicTo(iS, hisD, m, md, tresh, lev - 1, st, mt, piece); + AddPoint(m, piece, mt); + RecCubicTo(m, md, iE, hieD, tresh, lev - 1, mt, et, piece); + +} + + + +void Path::RecBezierTo(Geom::Point const &iP, + Geom::Point const &iS, + Geom::Point const &iE, + double tresh, int lev, double st, double et, int piece) +{ + if ( lev <= 0 ) { + return; + } + + Geom::Point ps = iS - iP; + Geom::Point pe = iE - iP; + const double s = fabs(cross(pe, ps)); + if ( s < tresh ) { + return; + } + + { + const double mt = (st + et) / 2; + const Geom::Point m = 0.25 * (iS + iE + 2 * iP); + RecBezierTo(0.5 * (iS + iP), iS, m, tresh, lev - 1, st, mt, piece); + AddPoint(m, piece, mt); + RecBezierTo(0.5 * (iP + iE), m, iE, tresh, lev - 1, mt, et, piece); + } +} + + + +void Path::DoArc(Geom::Point const &iS, Geom::Point const &iE, + double const rx, double const ry, double const angle, + bool const large, bool const wise, double const /*tresh*/, + int const piece, offset_orig &/*orig*/) +{ + // Will never arrive here, as offsets are made of cubics. + // [on n'arrivera jamais ici, puisque les offsets sont fait de cubiques] + /* TODO: Check that our behaviour is standards-conformant if iS and iE are (much) further + apart than the diameter. Also check that we do the right thing for negative radius. + (Same for the other DoArc functions in this file.) */ + if ( rx <= 0.0001 || ry <= 0.0001 ) { + return; + // We always add a lineto afterwards, so this is fine. + // [on ajoute toujours un lineto apres, donc c bon] + } + + double sang; + double eang; + Geom::Point dr_temp; + ArcAnglesAndCenter(iS, iE, rx, ry, angle*M_PI/180.0, large, wise, sang, eang, dr_temp); + Geom::Point dr = dr_temp; + /* TODO: This isn't as good numerically as treating iS and iE as primary. E.g. consider + the case of low curvature (i.e. very large radius). */ + + Geom::Scale const ar(rx, ry); + Geom::Rotate cb(sang); + Geom::Rotate cbangle(angle*M_PI/180.0); + if (wise) { + + double const incr = -0.1/sqrt(ar.vector().length()); + if ( sang < eang ) { + sang += 2*M_PI; + } + Geom::Rotate const omega(incr); + for (double b = sang + incr; b > eang ;b += incr) { + cb = omega * cb; + AddPoint(cb.vector() * ar * cbangle + dr, piece, (sang - b) / (sang - eang)); + } + + } else { + double const incr = 0.1/sqrt(ar.vector().length()); + if ( sang > eang ) { + sang -= 2*M_PI; + } + Geom::Rotate const omega(incr); + for (double b = sang + incr ; b < eang ; b += incr) { + cb = omega * cb; + AddPoint(cb.vector() * ar * cbangle + dr, piece, (b - sang) / (eang - sang)); + } + } +} + + +void Path::RecCubicTo(Geom::Point const &iS, Geom::Point const &isD, + Geom::Point const &iE, Geom::Point const &ieD, + double tresh, int lev, double st, double et, + int piece, offset_orig &orig) +{ + const Geom::Point se = iE - iS; + const double dC = Geom::L2(se); + bool doneSub = false; + if ( dC < 0.01 ) { + const double sC = dot(isD, isD); + const double eC = dot(ieD, ieD); + if ( sC < tresh && eC < tresh ) { + return; + } + } else { + const double sC = fabs(cross(se, isD)) / dC; + const double eC = fabs(cross(se, ieD)) / dC; + if ( sC < tresh && eC < tresh ) { + doneSub = true; + } + } + + if ( lev <= 0 ) { + doneSub = true; + } + + // test des inversions + bool stInv = false; + bool enInv = false; + { + Geom::Point os_pos; + Geom::Point os_tgt; + Geom::Point oe_pos; + Geom::Point oe_tgt; + + orig.orig->PointAndTangentAt(orig.piece, orig.tSt * (1 - st) + orig.tEn * st, os_pos, os_tgt); + orig.orig->PointAndTangentAt(orig.piece, orig.tSt * (1 - et) + orig.tEn * et, oe_pos, oe_tgt); + + + Geom::Point n_tgt = isD; + double si = dot(n_tgt, os_tgt); + if ( si < 0 ) { + stInv = true; + } + n_tgt = ieD; + si = dot(n_tgt, oe_tgt); + if ( si < 0 ) { + enInv = true; + } + if ( stInv && enInv ) { + + AddPoint(os_pos, -1, 0.0); + AddPoint(iE, piece, et); + AddPoint(iS, piece, st); + AddPoint(oe_pos, -1, 0.0); + return; + + } else if ( ( stInv && !enInv ) || ( !stInv && enInv ) ) { + return; + } + + } + + if ( ( !stInv && !enInv && doneSub ) || lev <= 0 ) { + return; + } + + { + const Geom::Point m = 0.5 * (iS+iE) + 0.125 * (isD - ieD); + const Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + const double mt = (st + et) / 2; + const Geom::Point hisD = 0.5 * isD; + const Geom::Point hieD = 0.5 * ieD; + + RecCubicTo(iS, hisD, m, md, tresh, lev - 1, st, mt, piece, orig); + AddPoint(m, piece, mt); + RecCubicTo(m, md, iE, hieD, tresh, lev - 1, mt, et, piece, orig); + } +} + + + +void Path::RecBezierTo(Geom::Point const &iP, Geom::Point const &iS,Geom::Point const &iE, + double tresh, int lev, double st, double et, + int piece, offset_orig& orig) +{ + bool doneSub = false; + if ( lev <= 0 ) { + return; + } + + const Geom::Point ps = iS - iP; + const Geom::Point pe = iE - iP; + const double s = fabs(cross(pe, ps)); + if ( s < tresh ) { + doneSub = true ; + } + + // test des inversions + bool stInv = false; + bool enInv = false; + { + Geom::Point os_pos; + Geom::Point os_tgt; + Geom::Point oe_pos; + Geom::Point oe_tgt; + Geom::Point n_tgt; + Geom::Point n_pos; + + double n_len; + double n_rad; + PathDescrIntermBezierTo mid(iP); + PathDescrBezierTo fin(iE, 1); + + TangentOnBezAt(0.0, iS, mid, fin, false, n_pos, n_tgt, n_len, n_rad); + orig.orig->PointAndTangentAt(orig.piece, orig.tSt * (1 - st) + orig.tEn * st, os_pos, os_tgt); + double si = dot(n_tgt, os_tgt); + if ( si < 0 ) { + stInv = true; + } + + TangentOnBezAt(1.0, iS, mid, fin, false, n_pos, n_tgt, n_len, n_rad); + orig.orig->PointAndTangentAt(orig.piece, orig.tSt * (1 - et) + orig.tEn * et, oe_pos, oe_tgt); + si = dot(n_tgt, oe_tgt); + if ( si < 0 ) { + enInv = true; + } + + if ( stInv && enInv ) { + AddPoint(os_pos, -1, 0.0); + AddPoint(iE, piece, et); + AddPoint(iS, piece, st); + AddPoint(oe_pos, -1, 0.0); + return; + } + } + + if ( !stInv && !enInv && doneSub ) { + return; + } + + { + double mt = (st + et) / 2; + Geom::Point m = 0.25 * (iS + iE + 2 * iP); + Geom::Point md = 0.5 * (iS + iP); + RecBezierTo(md, iS, m, tresh, lev - 1, st, mt, piece, orig); + AddPoint(m, piece, mt); + md = 0.5 * (iP + iE); + RecBezierTo(md, m, iE, tresh, lev - 1, mt, et, piece, orig); + } +} + + +/* + * put a polyline in a Shape instance, for further fun + * pathID is the ID you want this Path instance to be associated with, for when you're going to recompose the polyline + * in a path description ( you need to have prepared the back data for that, of course) + */ + +void Path::Fill(Shape* dest, int pathID, bool justAdd, bool closeIfNeeded, bool invert) +{ + if ( dest == nullptr ) { + return; + } + + if ( justAdd == false ) { + dest->Reset(pts.size(), pts.size()); + } + + if ( pts.size() <= 1 ) { + return; + } + + int first = dest->numberOfPoints(); + + if ( back ) { + dest->MakeBackData(true); + } + + if ( invert ) { + if ( back ) { + { + // invert && back && !weighted + for (auto & pt : pts) { + dest->AddPoint(pt.p); + } + int lastM = 0; + int curP = 1; + int pathEnd = 0; + bool closed = false; + int lEdge = -1; + + while ( curP < int(pts.size()) ) { + int sbp = curP; + int lm = lastM; + int prp = pathEnd; + + if ( pts[sbp].isMoveTo == polyline_moveto ) { + + if ( closeIfNeeded ) { + if ( closed && lEdge >= 0 ) { + dest->DisconnectStart(lEdge); + dest->ConnectStart(first + lastM, lEdge); + } else { + lEdge = dest->AddEdge(first + lastM, first+pathEnd); + if ( lEdge >= 0 ) { + dest->ebData[lEdge].pathID = pathID; + dest->ebData[lEdge].pieceID = pts[lm].piece; + dest->ebData[lEdge].tSt = 1.0; + dest->ebData[lEdge].tEn = 0.0; + } + } + } + + lastM = curP; + pathEnd = curP; + closed = false; + lEdge = -1; + + } else { + + if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) { + lEdge = dest->AddEdge(first + curP, first + pathEnd); + if ( lEdge >= 0 ) { + dest->ebData[lEdge].pathID = pathID; + dest->ebData[lEdge].pieceID = pts[sbp].piece; + if ( pts[sbp].piece == pts[prp].piece ) { + dest->ebData[lEdge].tSt = pts[sbp].t; + dest->ebData[lEdge].tEn = pts[prp].t; + } else { + dest->ebData[lEdge].tSt = pts[sbp].t; + dest->ebData[lEdge].tEn = 0.0; + } + } + pathEnd = curP; + if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) { + closed = true; + } else { + closed = false; + } + } + } + + curP++; + } + + if ( closeIfNeeded ) { + if ( closed && lEdge >= 0 ) { + dest->DisconnectStart(lEdge); + dest->ConnectStart(first + lastM, lEdge); + } else { + int lm = lastM; + lEdge = dest->AddEdge(first + lastM, first + pathEnd); + if ( lEdge >= 0 ) { + dest->ebData[lEdge].pathID = pathID; + dest->ebData[lEdge].pieceID = pts[lm].piece; + dest->ebData[lEdge].tSt = 1.0; + dest->ebData[lEdge].tEn = 0.0; + } + } + } + } + + } else { + + { + // invert && !back && !weighted + for (auto & pt : pts) { + dest->AddPoint(pt.p); + } + int lastM = 0; + int curP = 1; + int pathEnd = 0; + bool closed = false; + int lEdge = -1; + while ( curP < int(pts.size()) ) { + int sbp = curP; + int lm = lastM; + int prp = pathEnd; + if ( pts[sbp].isMoveTo == polyline_moveto ) { + if ( closeIfNeeded ) { + if ( closed && lEdge >= 0 ) { + dest->DisconnectStart(lEdge); + dest->ConnectStart(first + lastM, lEdge); + } else { + dest->AddEdge(first + lastM, first + pathEnd); + } + } + lastM = curP; + pathEnd = curP; + closed = false; + lEdge = -1; + } else { + if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) { + lEdge = dest->AddEdge(first+curP, first+pathEnd); + pathEnd = curP; + if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) { + closed = true; + } else { + closed = false; + } + } + } + curP++; + } + + if ( closeIfNeeded ) { + if ( closed && lEdge >= 0 ) { + dest->DisconnectStart(lEdge); + dest->ConnectStart(first + lastM, lEdge); + } else { + dest->AddEdge(first + lastM, first + pathEnd); + } + } + + } + } + + } else { + + if ( back ) { + { + // !invert && back && !weighted + + // add all points to the shape + for (auto & pt : pts) { + dest->AddPoint(pt.p); + } + + int lastM = 0; + int curP = 1; + int pathEnd = 0; + bool closed = false; + int lEdge = -1; + while ( curP < int(pts.size()) ) { + int sbp = curP; + int lm = lastM; + int prp = pathEnd; + if ( pts[sbp].isMoveTo == polyline_moveto ) { + if ( closeIfNeeded ) { + if ( closed && lEdge >= 0 ) { + dest->DisconnectEnd(lEdge); + dest->ConnectEnd(first + lastM, lEdge); + } else { + lEdge = dest->AddEdge(first + pathEnd, first+lastM); + if ( lEdge >= 0 ) { + dest->ebData[lEdge].pathID = pathID; + dest->ebData[lEdge].pieceID = pts[lm].piece; + dest->ebData[lEdge].tSt = 0.0; + dest->ebData[lEdge].tEn = 1.0; + } + } + } + lastM = curP; + pathEnd = curP; + closed = false; + lEdge = -1; + } else { + if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) { + lEdge = dest->AddEdge(first + pathEnd, first + curP); + dest->ebData[lEdge].pathID = pathID; + dest->ebData[lEdge].pieceID = pts[sbp].piece; + if ( pts[sbp].piece == pts[prp].piece ) { + dest->ebData[lEdge].tSt = pts[prp].t; + dest->ebData[lEdge].tEn = pts[sbp].t; + } else { + dest->ebData[lEdge].tSt = 0.0; + dest->ebData[lEdge].tEn = pts[sbp].t; + } + pathEnd = curP; + if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) { + closed = true; + } else { + closed = false; + } + } + } + curP++; + } + + if ( closeIfNeeded ) { + if ( closed && lEdge >= 0 ) { + dest->DisconnectEnd(lEdge); + dest->ConnectEnd(first + lastM, lEdge); + } else { + int lm = lastM; + lEdge = dest->AddEdge(first + pathEnd, first + lastM); + if ( lEdge >= 0 ) { + dest->ebData[lEdge].pathID = pathID; + dest->ebData[lEdge].pieceID = pts[lm].piece; + dest->ebData[lEdge].tSt = 0.0; + dest->ebData[lEdge].tEn = 1.0; + } + } + } + } + + } else { + { + // !invert && !back && !weighted + for (auto & pt : pts) { + dest->AddPoint(pt.p); + } + + int lastM = 0; + int curP = 1; + int pathEnd = 0; + bool closed = false; + int lEdge = -1; + while ( curP < int(pts.size()) ) { + int sbp = curP; + int lm = lastM; + int prp = pathEnd; + if ( pts[sbp].isMoveTo == polyline_moveto ) { + if ( closeIfNeeded ) { + if ( closed && lEdge >= 0 ) { + dest->DisconnectEnd(lEdge); + dest->ConnectEnd(first + lastM, lEdge); + } else { + dest->AddEdge(first + pathEnd, first + lastM); + } + } + lastM = curP; + pathEnd = curP; + closed = false; + lEdge = -1; + } else { + if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) { + lEdge = dest->AddEdge(first+pathEnd, first+curP); + pathEnd = curP; + if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) { + closed = true; + } else { + closed = false; + } + } + } + curP++; + } + + if ( closeIfNeeded ) { + if ( closed && lEdge >= 0 ) { + dest->DisconnectEnd(lEdge); + dest->ConnectEnd(first + lastM, lEdge); + } else { + dest->AddEdge(first + pathEnd, first + lastM); + } + } + + } + } + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/livarot/PathCutting.cpp b/src/livarot/PathCutting.cpp new file mode 100644 index 0000000..5978126 --- /dev/null +++ b/src/livarot/PathCutting.cpp @@ -0,0 +1,1529 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PathCutting.cpp + * nlivarot + * + * Created by fred on someday in 2004. + * public domain + * + * Additional Code by Authors: + * Richard Hughes <cyreve@users.sf.net> + * + * Copyright (C) 2005 Richard Hughes + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cmath> +#include <cstring> +#include <string> +#include <cstdio> +#include <typeinfo> +#include "Path.h" +#include "style.h" +#include "livarot/path-description.h" +#include <2geom/pathvector.h> +#include <2geom/point.h> +#include <2geom/affine.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/curves.h> +#include <vector> +#include "helper/geom-curves.h" +#include "helper/geom.h" + +#include "svg/svg.h" + +void Path::DashPolyline(float head,float tail,float body,int nbD, const float dashs[],bool stPlain,float stOffset) +{ + if ( nbD <= 0 || body <= 0.0001 ) return; // pas de tirets, en fait + + std::vector<path_lineto> orig_pts = pts; + pts.clear(); + + int lastMI=-1; + int curP = 0; + int lastMP = -1; + + for (int i = 0; i < int(orig_pts.size()); i++) { + if ( orig_pts[curP].isMoveTo == polyline_moveto ) { + if ( lastMI >= 0 && lastMI < i-1 ) { // au moins 2 points + DashSubPath(i-lastMI,lastMP, orig_pts, head,tail,body,nbD,dashs,stPlain,stOffset); + } + lastMI=i; + lastMP=curP; + } + curP++; + } + if ( lastMI >= 0 && lastMI < int(orig_pts.size()) - 1 ) { + DashSubPath(orig_pts.size() - lastMI, lastMP, orig_pts, head, tail, body, nbD, dashs, stPlain, stOffset); + } +} + +void Path::DashPolylineFromStyle(SPStyle *style, float scale, float min_len) +{ + if (style->stroke_dasharray.values.empty() || !style->stroke_dasharray.is_valid()) return; + + double dlen = 0.0; + // Find total length + for (auto & value : style->stroke_dasharray.values) { + dlen += value.value * scale; + } + if (dlen >= min_len) { + // Extract out dash pattern (relative positions) + double dash_offset = style->stroke_dashoffset.value * scale; + size_t n_dash = style->stroke_dasharray.values.size(); + std::vector<double> dash(n_dash); + for (unsigned i = 0; i < n_dash; i++) { + dash[i] = style->stroke_dasharray.values[i].value * scale; + } + + // Convert relative positions to absolute positions + int nbD = n_dash; + std::vector<float> dashes(n_dash); + if (dlen > 0) { + while (dash_offset >= dlen) dash_offset -= dlen; + } + dashes[0] = dash[0]; + for (int i = 1; i < nbD; ++i) { + dashes[i] = dashes[i-1] + dash[i]; + } + + // modulo dlen + DashPolyline(0.0, 0.0, dlen, nbD, dashes.data(), true, dash_offset); + } +} + + +void Path::DashSubPath(int spL, int spP, std::vector<path_lineto> const &orig_pts, float head,float tail,float body,int nbD, const float dashs[],bool stPlain,float stOffset) +{ + if ( spL <= 0 || spP == -1 ) return; + + double totLength=0; + Geom::Point lastP; + lastP = orig_pts[spP].p; + for (int i=1;i<spL;i++) { + Geom::Point const n = orig_pts[spP + i].p; + Geom::Point d=n-lastP; + double nl=Geom::L2(d); + if ( nl > 0.0001 ) { + totLength+=nl; + lastP=n; + } + } + + if ( totLength <= head+tail ) return; // tout mange par la tete et la queue + + double curLength=0; + double dashPos=0; + int dashInd=0; + bool dashPlain=false; + double lastT=0; + int lastPiece=-1; + lastP = orig_pts[spP].p; + for (int i=1;i<spL;i++) { + Geom::Point n; + int nPiece=-1; + double nT=0; + if ( back ) { + n = orig_pts[spP + i].p; + nPiece = orig_pts[spP + i].piece; + nT = orig_pts[spP + i].t; + } else { + n = orig_pts[spP + i].p; + } + Geom::Point d=n-lastP; + double nl=Geom::L2(d); + if ( nl > 0.0001 ) { + double stLength=curLength; + double enLength=curLength+nl; + // couper les bouts en trop + if ( curLength <= head && curLength+nl > head ) { + nl-=head-curLength; + curLength=head; + dashInd=0; + dashPos=stOffset; + bool nPlain=stPlain; + while ( dashs[dashInd] < stOffset ) { + dashInd++; + nPlain=!(nPlain); + if ( dashInd >= nbD ) { + dashPos=0; + dashInd=0; + break; + } + } + if ( nPlain == true && dashPlain == false ) { + Geom::Point p=(enLength-curLength)*lastP+(curLength-stLength)*n; + p/=(enLength-stLength); + if ( back ) { + double pT=0; + if ( nPiece == lastPiece ) { + pT=(lastT*(enLength-curLength)+nT*(curLength-stLength))/(enLength-stLength); + } else { + pT=(nPiece*(curLength-stLength))/(enLength-stLength); + } + AddPoint(p,nPiece,pT,true); + } else { + AddPoint(p,true); + } + } else if ( nPlain == false && dashPlain == true ) { + } + dashPlain=nPlain; + } + // faire les tirets + if ( curLength >= head /*&& curLength+nl <= totLength-tail*/ ) { + while ( curLength <= totLength-tail && nl > 0 ) { + if ( enLength <= totLength-tail ) nl=enLength-curLength; else nl=totLength-tail-curLength; + double leftInDash=body-dashPos; + if ( dashInd < nbD ) { + leftInDash=dashs[dashInd]-dashPos; + } + if ( leftInDash <= nl ) { + bool nPlain=false; + if ( dashInd < nbD ) { + dashPos=dashs[dashInd]; + dashInd++; + if ( dashPlain ) nPlain=false; else nPlain=true; + } else { + dashInd=0; + dashPos=0; + //nPlain=stPlain; + nPlain=dashPlain; + } + if ( nPlain == true && dashPlain == false ) { + Geom::Point p=(enLength-curLength-leftInDash)*lastP+(curLength+leftInDash-stLength)*n; + p/=(enLength-stLength); + if ( back ) { + double pT=0; + if ( nPiece == lastPiece ) { + pT=(lastT*(enLength-curLength-leftInDash)+nT*(curLength+leftInDash-stLength))/(enLength-stLength); + } else { + pT=(nPiece*(curLength+leftInDash-stLength))/(enLength-stLength); + } + AddPoint(p,nPiece,pT,true); + } else { + AddPoint(p,true); + } + } else if ( nPlain == false && dashPlain == true ) { + Geom::Point p=(enLength-curLength-leftInDash)*lastP+(curLength+leftInDash-stLength)*n; + p/=(enLength-stLength); + if ( back ) { + double pT=0; + if ( nPiece == lastPiece ) { + pT=(lastT*(enLength-curLength-leftInDash)+nT*(curLength+leftInDash-stLength))/(enLength-stLength); + } else { + pT=(nPiece*(curLength+leftInDash-stLength))/(enLength-stLength); + } + AddPoint(p,nPiece,pT,false); + } else { + AddPoint(p,false); + } + } + dashPlain=nPlain; + + curLength+=leftInDash; + nl-=leftInDash; + } else { + dashPos+=nl; + curLength+=nl; + nl=0; + } + } + if ( dashPlain ) { + if ( back ) { + AddPoint(n,nPiece,nT,false); + } else { + AddPoint(n,false); + } + } + nl=enLength-curLength; + } + if ( curLength <= totLength-tail && curLength+nl > totLength-tail ) { + nl=totLength-tail-curLength; + dashInd=0; + dashPos=0; + bool nPlain=false; + if ( nPlain == true && dashPlain == false ) { + } else if ( nPlain == false && dashPlain == true ) { + Geom::Point p=(enLength-curLength)*lastP+(curLength-stLength)*n; + p/=(enLength-stLength); + if ( back ) { + double pT=0; + if ( nPiece == lastPiece ) { + pT=(lastT*(enLength-curLength)+nT*(curLength-stLength))/(enLength-stLength); + } else { + pT=(nPiece*(curLength-stLength))/(enLength-stLength); + } + AddPoint(p,nPiece,pT,false); + } else { + AddPoint(p,false); + } + } + dashPlain=nPlain; + } + // continuer + curLength=enLength; + lastP=n; + lastPiece=nPiece; + lastT=nT; + } + } +} + +/** + * Make a Geom::PathVector version of the path description. + * + * \return A PathVector copy of the path description + */ +Geom::PathVector +Path::MakePathVector() +{ + Geom::PathVector pv; + Geom::Path * currentpath = nullptr; + + Geom::Point lastP,bezSt,bezEn; + int bezNb=0; + for (int i=0;i<int(descr_cmd.size());i++) { + int const typ = descr_cmd[i]->getType(); + switch ( typ ) { + case descr_close: + { + currentpath->close(true); + } + break; + + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]); + currentpath->appendNew<Geom::LineSegment>(Geom::Point(nData->p[0], nData->p[1])); + lastP = nData->p; + } + break; + + case descr_moveto: + { + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]); + pv.push_back(Geom::Path()); + currentpath = &pv.back(); + currentpath->start(Geom::Point(nData->p[0], nData->p[1])); + lastP = nData->p; + } + break; + + case descr_arcto: + { + /* TODO: add testcase for this descr_arcto case */ + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]); + currentpath->appendNew<Geom::EllipticalArc>( nData->rx, nData->ry, nData->angle*M_PI/180.0, nData->large, !nData->clockwise, nData->p ); + lastP = nData->p; + } + break; + + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]); + gdouble x1=lastP[0]+0.333333*nData->start[0]; + gdouble y1=lastP[1]+0.333333*nData->start[1]; + gdouble x2=nData->p[0]-0.333333*nData->end[0]; + gdouble y2=nData->p[1]-0.333333*nData->end[1]; + gdouble x3=nData->p[0]; + gdouble y3=nData->p[1]; + currentpath->appendNew<Geom::CubicBezier>( Geom::Point(x1,y1) , Geom::Point(x2,y2) , Geom::Point(x3,y3) ); + lastP = nData->p; + } + break; + + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]); + if ( nData->nb <= 0 ) { + currentpath->appendNew<Geom::LineSegment>( Geom::Point(nData->p[0], nData->p[1]) ); + bezNb=0; + } else if ( nData->nb == 1 ){ + PathDescrIntermBezierTo *iData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i+1]); + gdouble x1=0.333333*(lastP[0]+2*iData->p[0]); + gdouble y1=0.333333*(lastP[1]+2*iData->p[1]); + gdouble x2=0.333333*(nData->p[0]+2*iData->p[0]); + gdouble y2=0.333333*(nData->p[1]+2*iData->p[1]); + gdouble x3=nData->p[0]; + gdouble y3=nData->p[1]; + currentpath->appendNew<Geom::CubicBezier>( Geom::Point(x1,y1) , Geom::Point(x2,y2) , Geom::Point(x3,y3) ); + bezNb=0; + } else { + bezSt = 2*lastP-nData->p; + bezEn = nData->p; + bezNb = nData->nb; + } + lastP = nData->p; + } + break; + + case descr_interm_bezier: + { + if ( bezNb > 0 ) { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]); + Geom::Point p_m=nData->p,p_s=0.5*(bezSt+p_m),p_e; + if ( bezNb > 1 ) { + PathDescrIntermBezierTo *iData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i+1]); + p_e=0.5*(p_m+iData->p); + } else { + p_e=bezEn; + } + + Geom::Point cp1=0.333333*(p_s+2*p_m),cp2=0.333333*(2*p_m+p_e); + gdouble x1=cp1[0]; + gdouble y1=cp1[1]; + gdouble x2=cp2[0]; + gdouble y2=cp2[1]; + gdouble x3=p_e[0]; + gdouble y3=p_e[1]; + currentpath->appendNew<Geom::CubicBezier>( Geom::Point(x1,y1) , Geom::Point(x2,y2) , Geom::Point(x3,y3) ); + + bezNb--; + } + } + break; + } + } + + return pv; +} + +void Path::AddCurve(Geom::Curve const &c) +{ + if( is_straight_curve(c) ) + { + LineTo( c.finalPoint() ); + } + /* + else if(Geom::QuadraticBezier const *quadratic_bezier = dynamic_cast<Geom::QuadraticBezier const *>(c)) { + ... + } + */ + else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const *>(&c)) { + Geom::Point tmp = (*cubic_bezier)[3]; + Geom::Point tms = 3 * ((*cubic_bezier)[1] - (*cubic_bezier)[0]); + Geom::Point tme = 3 * ((*cubic_bezier)[3] - (*cubic_bezier)[2]); + CubicTo (tmp, tms, tme); + } + else if(Geom::EllipticalArc const *elliptical_arc = dynamic_cast<Geom::EllipticalArc const *>(&c)) { + ArcTo( elliptical_arc->finalPoint(), + elliptical_arc->ray(Geom::X), elliptical_arc->ray(Geom::Y), + elliptical_arc->rotationAngle()*180.0/M_PI, // convert from radians to degrees + elliptical_arc->largeArc(), !elliptical_arc->sweep() ); + } else { + //this case handles sbasis as well as all other curve types + Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1); + + //recurse to convert the new path resulting from the sbasis to svgd + for(const auto & iter : sbasis_path) { + AddCurve(iter); + } + } +} + +/** append is false by default: it means that the path should be resetted. If it is true, the path is not resetted and Geom::Path will be appended as a new path + */ +void Path::LoadPath(Geom::Path const &path, Geom::Affine const &tr, bool doTransformation, bool append) +{ + if (!append) { + SetBackData (false); + Reset(); + } + if (path.empty()) + return; + + // TODO: this can be optimized by not generating a new path here, but doing the transform in AddCurve + // directly on the curve parameters + + Geom::Path const pathtr = doTransformation ? path * tr : path; + + MoveTo( pathtr.initialPoint() ); + + for(const auto & cit : pathtr) { + AddCurve(cit); + } + + if (pathtr.closed()) { + Close(); + } +} + +void Path::LoadPathVector(Geom::PathVector const &pv) +{ + LoadPathVector(pv, Geom::Affine(), false); +} + +void Path::LoadPathVector(Geom::PathVector const &pv, Geom::Affine const &tr, bool doTransformation) +{ + SetBackData (false); + Reset(); + + for(const auto & it : pv) { + LoadPath(it, tr, doTransformation, true); + } +} + +/** + * \return Length of the lines in the pts vector. + */ + +double Path::Length() +{ + if ( pts.empty() ) { + return 0; + } + + Geom::Point lastP = pts[0].p; + + double len = 0; + for (const auto & pt : pts) { + + if ( pt.isMoveTo != polyline_moveto ) { + len += Geom::L2(pt.p - lastP); + } + + lastP = pt.p; + } + + return len; +} + + +double Path::Surface() +{ + if ( pts.empty() ) { + return 0; + } + + Geom::Point lastM = pts[0].p; + Geom::Point lastP = lastM; + + double surf = 0; + for (const auto & pt : pts) { + + if ( pt.isMoveTo == polyline_moveto ) { + surf += Geom::cross(lastM, lastM - lastP); + lastP = lastM = pt.p; + } else { + surf += Geom::cross(pt.p, pt.p - lastP); + lastP = pt.p; + } + + } + + return surf; +} + + +Path** Path::SubPaths(int &outNb,bool killNoSurf) +{ + int nbRes=0; + Path** res=nullptr; + Path* curAdd=nullptr; + + for (auto & i : descr_cmd) { + int const typ = i->getType(); + switch ( typ ) { + case descr_moveto: + if ( curAdd ) { + if ( curAdd->descr_cmd.size() > 1 ) { + curAdd->Convert(1.0); + double addSurf=curAdd->Surface(); + if ( fabs(addSurf) > 0.0001 || killNoSurf == false ) { + res=(Path**)g_realloc(res,(nbRes+1)*sizeof(Path*)); + res[nbRes++]=curAdd; + } else { + delete curAdd; + } + } else { + delete curAdd; + } + curAdd=nullptr; + } + curAdd=new Path; + curAdd->SetBackData(false); + { + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(i); + curAdd->MoveTo(nData->p); + } + break; + case descr_close: + { + curAdd->Close(); + } + break; + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(i); + curAdd->LineTo(nData->p); + } + break; + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(i); + curAdd->CubicTo(nData->p,nData->start,nData->end); + } + break; + case descr_arcto: + { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(i); + curAdd->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise); + } + break; + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(i); + curAdd->BezierTo(nData->p); + } + break; + case descr_interm_bezier: + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(i); + curAdd->IntermBezierTo(nData->p); + } + break; + default: + break; + } + } + if ( curAdd ) { + if ( curAdd->descr_cmd.size() > 1 ) { + curAdd->Convert(1.0); + double addSurf=curAdd->Surface(); + if ( fabs(addSurf) > 0.0001 || killNoSurf == false ) { + res=(Path**)g_realloc(res,(nbRes+1)*sizeof(Path*)); + res[nbRes++]=curAdd; + } else { + delete curAdd; + } + } else { + delete curAdd; + } + } + curAdd=nullptr; + + outNb=nbRes; + return res; +} +Path** Path::SubPathsWithNesting(int &outNb,bool killNoSurf,int nbNest,int* nesting,int* conts) +{ + int nbRes=0; + Path** res=nullptr; + Path* curAdd=nullptr; + bool increment=false; + + for (int i=0;i<int(descr_cmd.size());i++) { + int const typ = descr_cmd[i]->getType(); + switch ( typ ) { + case descr_moveto: + { + if ( curAdd && increment == false ) { + if ( curAdd->descr_cmd.size() > 1 ) { + // sauvegarder descr_cmd[0]->associated + int savA=curAdd->descr_cmd[0]->associated; + curAdd->Convert(1.0); + curAdd->descr_cmd[0]->associated=savA; // associated n'est pas utilise apres + double addSurf=curAdd->Surface(); + if ( fabs(addSurf) > 0.0001 || killNoSurf == false ) { + res=(Path**)g_realloc(res,(nbRes+1)*sizeof(Path*)); + res[nbRes++]=curAdd; + } else { + delete curAdd; + } + } else { + delete curAdd; + } + curAdd=nullptr; + } + Path* hasParent=nullptr; + for (int j=0;j<nbNest;j++) { + if ( conts[j] == i && nesting[j] >= 0 ) { + int parentMvt=conts[nesting[j]]; + for (int k=0;k<nbRes;k++) { + if ( res[k] && res[k]->descr_cmd.empty() == false && res[k]->descr_cmd[0]->associated == parentMvt ) { + hasParent=res[k]; + break; + } + } + } + if ( conts[j] > i ) break; + } + if ( hasParent ) { + curAdd=hasParent; + increment=true; + } else { + curAdd=new Path; + curAdd->SetBackData(false); + increment=false; + } + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]); + int mNo=curAdd->MoveTo(nData->p); + curAdd->descr_cmd[mNo]->associated=i; + } + break; + case descr_close: + { + curAdd->Close(); + } + break; + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]); + curAdd->LineTo(nData->p); + } + break; + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]); + curAdd->CubicTo(nData->p,nData->start,nData->end); + } + break; + case descr_arcto: + { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]); + curAdd->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise); + } + break; + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]); + curAdd->BezierTo(nData->p); + } + break; + case descr_interm_bezier: + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]); + curAdd->IntermBezierTo(nData->p); + } + break; + default: + break; + } + } + if ( curAdd && increment == false ) { + if ( curAdd->descr_cmd.size() > 1 ) { + curAdd->Convert(1.0); + double addSurf=curAdd->Surface(); + if ( fabs(addSurf) > 0.0001 || killNoSurf == false ) { + res=(Path**)g_realloc(res,(nbRes+1)*sizeof(Path*)); + res[nbRes++]=curAdd; + } else { + delete curAdd; + } + } else { + delete curAdd; + } + } + curAdd=nullptr; + + outNb=nbRes; + return res; +} + + +void Path::ConvertForcedToVoid() +{ + for (int i=0; i < int(descr_cmd.size()); i++) { + if ( descr_cmd[i]->getType() == descr_forced) { + delete descr_cmd[i]; + descr_cmd.erase(descr_cmd.begin() + i); + } + } +} + + +void Path::ConvertForcedToMoveTo() +{ + Geom::Point lastSeen(0, 0); + Geom::Point lastMove(0, 0); + + { + Geom::Point lastPos(0, 0); + for (int i = int(descr_cmd.size()) - 1; i >= 0; i--) { + int const typ = descr_cmd[i]->getType(); + switch ( typ ) { + case descr_forced: + { + PathDescrForced *d = dynamic_cast<PathDescrForced *>(descr_cmd[i]); + d->p = lastPos; + break; + } + case descr_close: + { + PathDescrClose *d = dynamic_cast<PathDescrClose *>(descr_cmd[i]); + d->p = lastPos; + break; + } + case descr_moveto: + { + PathDescrMoveTo *d = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_lineto: + { + PathDescrLineTo *d = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_arcto: + { + PathDescrArcTo *d = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_cubicto: + { + PathDescrCubicTo *d = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_bezierto: + { + PathDescrBezierTo *d = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_interm_bezier: + { + PathDescrIntermBezierTo *d = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + default: + break; + } + } + } + + bool hasMoved = false; + for (int i = 0; i < int(descr_cmd.size()); i++) { + int const typ = descr_cmd[i]->getType(); + switch ( typ ) { + case descr_forced: + if ( i < int(descr_cmd.size()) - 1 && hasMoved ) { // sinon il termine le chemin + + delete descr_cmd[i]; + descr_cmd[i] = new PathDescrMoveTo(lastSeen); + lastMove = lastSeen; + hasMoved = true; + } + break; + + case descr_moveto: + { + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]); + lastMove = lastSeen = nData->p; + hasMoved = true; + } + break; + case descr_close: + { + lastSeen=lastMove; + } + break; + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]); + lastSeen=nData->p; + } + break; + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]); + lastSeen=nData->p; + } + break; + case descr_arcto: + { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]); + lastSeen=nData->p; + } + break; + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]); + lastSeen=nData->p; + } + break; + case descr_interm_bezier: + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]); + lastSeen=nData->p; + } + break; + default: + break; + } + } +} +static int CmpPosition(const void * p1, const void * p2) { + Path::cut_position *cp1=(Path::cut_position*)p1; + Path::cut_position *cp2=(Path::cut_position*)p2; + if ( cp1->piece < cp2->piece ) return -1; + if ( cp1->piece > cp2->piece ) return 1; + if ( cp1->t < cp2->t ) return -1; + if ( cp1->t > cp2->t ) return 1; + return 0; +} +static int CmpCurv(const void * p1, const void * p2) { + double *cp1=(double*)p1; + double *cp2=(double*)p2; + if ( *cp1 < *cp2 ) return -1; + if ( *cp1 > *cp2 ) return 1; + return 0; +} + + +Path::cut_position* Path::CurvilignToPosition(int nbCv, double *cvAbs, int &nbCut) +{ + if ( nbCv <= 0 || pts.empty() || back == false ) { + return nullptr; + } + + qsort(cvAbs, nbCv, sizeof(double), CmpCurv); + + cut_position *res = nullptr; + nbCut = 0; + int curCv = 0; + + double len = 0; + double lastT = 0; + int lastPiece = -1; + + Geom::Point lastM = pts[0].p; + Geom::Point lastP = lastM; + + for (const auto & pt : pts) { + + if ( pt.isMoveTo == polyline_moveto ) { + + lastP = lastM = pt.p; + lastT = pt.t; + lastPiece = pt.piece; + + } else { + + double const add = Geom::L2(pt.p - lastP); + double curPos = len; + double curAdd = add; + + while ( curAdd > 0.0001 && curCv < nbCv && curPos + curAdd >= cvAbs[curCv] ) { + double const theta = (cvAbs[curCv] - len) / add; + res = (cut_position*) g_realloc(res, (nbCut + 1) * sizeof(cut_position)); + res[nbCut].piece = pt.piece; + res[nbCut].t = theta * pt.t + (1 - theta) * ( (lastPiece != pt.piece) ? 0 : lastT); + nbCut++; + curAdd -= cvAbs[curCv] - curPos; + curPos = cvAbs[curCv]; + curCv++; + } + + len += add; + lastPiece = pt.piece; + lastP = pt.p; + lastT = pt.t; + } + } + + return res; +} + +/* +Moved from Layout-TNG-OutIter.cpp +TODO: clean up uses of the original function and remove + +Original Comment: +"this function really belongs to Path. I'll probably move it there eventually, +hence the Path-esque coding style" + +*/ +template<typename T> inline static T square(T x) {return x*x;} +Path::cut_position Path::PointToCurvilignPosition(Geom::Point const &pos, unsigned seg) const +{ + // if the parameter "seg" == 0, then all segments will be considered + // In however e.g. "seg" == 6 , then only the 6th segment will be considered + + unsigned bestSeg = 0; + double bestRangeSquared = DBL_MAX; + double bestT = 0.0; // you need a sentinel, or make sure that you prime with correct values. + + for (unsigned i = 1 ; i < pts.size() ; i++) { + if (pts[i].isMoveTo == polyline_moveto || (seg > 0 && i != seg)) continue; + Geom::Point p1, p2, localPos; + double thisRangeSquared; + double t; + + if (pts[i - 1].p == pts[i].p) { + thisRangeSquared = square(pts[i].p[Geom::X] - pos[Geom::X]) + square(pts[i].p[Geom::Y] - pos[Geom::Y]); + t = 0.0; + } else { + // we rotate all our coordinates so we're always looking at a mostly vertical line. + if (fabs(pts[i - 1].p[Geom::X] - pts[i].p[Geom::X]) < fabs(pts[i - 1].p[Geom::Y] - pts[i].p[Geom::Y])) { + p1 = pts[i - 1].p; + p2 = pts[i].p; + localPos = pos; + } else { + p1 = pts[i - 1].p.cw(); + p2 = pts[i].p.cw(); + localPos = pos.cw(); + } + double gradient = (p2[Geom::X] - p1[Geom::X]) / (p2[Geom::Y] - p1[Geom::Y]); + double intersection = p1[Geom::X] - gradient * p1[Geom::Y]; + /* + orthogonalGradient = -1.0 / gradient; // you are going to have numerical problems here. + orthogonalIntersection = localPos[Geom::X] - orthogonalGradient * localPos[Geom::Y]; + nearestY = (orthogonalIntersection - intersection) / (gradient - orthogonalGradient); + + expand out nearestY fully : + nearestY = (localPos[Geom::X] - (-1.0 / gradient) * localPos[Geom::Y] - intersection) / (gradient - (-1.0 / gradient)); + + multiply top and bottom by gradient: + nearestY = (localPos[Geom::X] * gradient - (-1.0) * localPos[Geom::Y] - intersection * gradient) / (gradient * gradient - (-1.0)); + + and simplify to get: + */ + double nearestY = (localPos[Geom::X] * gradient + localPos[Geom::Y] - intersection * gradient) + / (gradient * gradient + 1.0); + t = (nearestY - p1[Geom::Y]) / (p2[Geom::Y] - p1[Geom::Y]); + if (t <= 0.0) { + thisRangeSquared = square(p1[Geom::X] - localPos[Geom::X]) + square(p1[Geom::Y] - localPos[Geom::Y]); + t = 0.0; + } else if (t >= 1.0) { + thisRangeSquared = square(p2[Geom::X] - localPos[Geom::X]) + square(p2[Geom::Y] - localPos[Geom::Y]); + t = 1.0; + } else { + thisRangeSquared = square(nearestY * gradient + intersection - localPos[Geom::X]) + square(nearestY - localPos[Geom::Y]); + } + } + + if (thisRangeSquared < bestRangeSquared) { + bestSeg = i; + bestRangeSquared = thisRangeSquared; + bestT = t; + } + } + Path::cut_position result; + if (bestSeg == 0) { + result.piece = 0; + result.t = 0.0; + } else { + result.piece = pts[bestSeg].piece; + if (result.piece == pts[bestSeg - 1].piece) { + result.t = pts[bestSeg - 1].t * (1.0 - bestT) + pts[bestSeg].t * bestT; + } else { + result.t = pts[bestSeg].t * bestT; + } + } + return result; +} +/* + this one also belongs to Path + returns the length of the path up to the position indicated by t (0..1) + + TODO: clean up uses of the original function and remove + + should this take a cut_position as a parameter? +*/ +double Path::PositionToLength(int piece, double t) +{ + double length = 0.0; + for (unsigned i = 1 ; i < pts.size() ; i++) { + if (pts[i].isMoveTo == polyline_moveto) continue; + if (pts[i].piece == piece && t < pts[i].t) { + length += Geom::L2((t - pts[i - 1].t) / (pts[i].t - pts[i - 1].t) * (pts[i].p - pts[i - 1].p)); + break; + } + length += Geom::L2(pts[i].p - pts[i - 1].p); + } + return length; +} + +void Path::ConvertPositionsToForced(int nbPos, cut_position *poss) +{ + if ( nbPos <= 0 ) { + return; + } + + { + Geom::Point lastPos(0, 0); + for (int i = int(descr_cmd.size()) - 1; i >= 0; i--) { + int const typ = descr_cmd[i]->getType(); + switch ( typ ) { + + case descr_forced: + { + PathDescrForced *d = dynamic_cast<PathDescrForced *>(descr_cmd[i]); + d->p = lastPos; + break; + } + + case descr_close: + { + delete descr_cmd[i]; + descr_cmd[i] = new PathDescrLineTo(Geom::Point(0, 0)); + + int fp = i - 1; + while ( fp >= 0 && (descr_cmd[fp]->getType()) != descr_moveto ) { + fp--; + } + + if ( fp >= 0 ) { + PathDescrMoveTo *oData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[fp]); + dynamic_cast<PathDescrLineTo*>(descr_cmd[i])->p = oData->p; + } + } + break; + + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]); + Geom::Point theP = nData->p; + if ( nData->nb == 0 ) { + lastPos = theP; + } + } + break; + + case descr_moveto: + { + PathDescrMoveTo *d = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_lineto: + { + PathDescrLineTo *d = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_arcto: + { + PathDescrArcTo *d = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_cubicto: + { + PathDescrCubicTo *d = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_interm_bezier: + { + PathDescrIntermBezierTo *d = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]); + lastPos = d->p; + break; + } + default: + break; + } + } + } + if (descr_cmd[0]->getType() == descr_moveto) + descr_flags |= descr_doing_subpath; // see LP Bug 166302 + + qsort(poss, nbPos, sizeof(cut_position), CmpPosition); + + for (int curP=0;curP<nbPos;curP++) { + int cp=poss[curP].piece; + if ( cp < 0 || cp >= int(descr_cmd.size()) ) break; + float ct=poss[curP].t; + if ( ct < 0 ) continue; + if ( ct > 1 ) continue; + + int const typ = descr_cmd[cp]->getType(); + if ( typ == descr_moveto || typ == descr_forced || typ == descr_close ) { + // ponctuel= rien a faire + } else if ( typ == descr_lineto || typ == descr_arcto || typ == descr_cubicto ) { + // facile: creation d'un morceau et d'un forced -> 2 commandes + Geom::Point theP; + Geom::Point theT; + Geom::Point startP; + startP=PrevPoint(cp-1); + if ( typ == descr_cubicto ) { + double len,rad; + Geom::Point stD,enD,endP; + { + PathDescrCubicTo *oData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[cp]); + stD=oData->start; + enD=oData->end; + endP=oData->p; + TangentOnCubAt (ct, startP, *oData,true, theP,theT,len,rad); + } + + theT*=len; + + InsertCubicTo(endP,(1-ct)*theT,(1-ct)*enD,cp+1); + InsertForcePoint(cp+1); + { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[cp]); + nData->start=ct*stD; + nData->end=ct*theT; + nData->p=theP; + } + // decalages dans le tableau des positions de coupe + for (int j=curP+1;j<nbPos;j++) { + if ( poss[j].piece == cp ) { + poss[j].piece+=2; + poss[j].t=(poss[j].t-ct)/(1-ct); + } else { + poss[j].piece+=2; + } + } + } else if ( typ == descr_lineto ) { + Geom::Point endP; + { + PathDescrLineTo *oData = dynamic_cast<PathDescrLineTo *>(descr_cmd[cp]); + endP=oData->p; + } + + theP=ct*endP+(1-ct)*startP; + + InsertLineTo(endP,cp+1); + InsertForcePoint(cp+1); + { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[cp]); + nData->p=theP; + } + // decalages dans le tableau des positions de coupe + for (int j=curP+1;j<nbPos;j++) { + if ( poss[j].piece == cp ) { + poss[j].piece+=2; + poss[j].t=(poss[j].t-ct)/(1-ct); + } else { + poss[j].piece+=2; + } + } + } else if ( typ == descr_arcto ) { + Geom::Point endP; + double rx,ry,angle; + bool clockw,large; + double delta=0; + { + PathDescrArcTo *oData = dynamic_cast<PathDescrArcTo *>(descr_cmd[cp]); + endP=oData->p; + rx=oData->rx; + ry=oData->ry; + angle=oData->angle; + clockw=oData->clockwise; + large=oData->large; + } + { + double sang,eang; + ArcAngles(startP,endP,rx,ry,angle*M_PI/180.0,large,clockw,sang,eang); + + if (clockw) { + if ( sang < eang ) sang += 2*M_PI; + delta=eang-sang; + } else { + if ( sang > eang ) sang -= 2*M_PI; + delta=eang-sang; + } + if ( delta < 0 ) delta=-delta; + } + + PointAt (cp,ct, theP); + + if ( delta*(1-ct) > M_PI ) { + InsertArcTo(endP,rx,ry,angle,true,clockw,cp+1); + } else { + InsertArcTo(endP,rx,ry,angle,false,clockw,cp+1); + } + InsertForcePoint(cp+1); + { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[cp]); + nData->p=theP; + if ( delta*ct > M_PI ) { + nData->large=true; + } else { + nData->large=false; + } + } + // decalages dans le tableau des positions de coupe + for (int j=curP+1;j<nbPos;j++) { + if ( poss[j].piece == cp ) { + poss[j].piece+=2; + poss[j].t=(poss[j].t-ct)/(1-ct); + } else { + poss[j].piece+=2; + } + } + } + } else if ( typ == descr_bezierto || typ == descr_interm_bezier ) { + // dur + int theBDI=cp; + while ( theBDI >= 0 && (descr_cmd[theBDI]->getType()) != descr_bezierto ) theBDI--; + if ( (descr_cmd[theBDI]->getType()) == descr_bezierto ) { + PathDescrBezierTo theBD=*(dynamic_cast<PathDescrBezierTo *>(descr_cmd[theBDI])); + if ( cp >= theBDI && cp < theBDI+theBD.nb ) { + if ( theBD.nb == 1 ) { + Geom::Point endP=theBD.p; + Geom::Point midP; + Geom::Point startP; + startP=PrevPoint(theBDI-1); + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[theBDI+1]); + midP=nData->p; + } + Geom::Point aP=ct*midP+(1-ct)*startP; + Geom::Point bP=ct*endP+(1-ct)*midP; + Geom::Point knotP=ct*bP+(1-ct)*aP; + + InsertIntermBezierTo(bP,theBDI+2); + InsertBezierTo(knotP,1,theBDI+2); + InsertForcePoint(theBDI+2); + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[theBDI+1]); + nData->p=aP; + } + { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[theBDI]); + nData->p=knotP; + } + // decalages dans le tableau des positions de coupe + for (int j=curP+1;j<nbPos;j++) { + if ( poss[j].piece == cp ) { + poss[j].piece+=3; + poss[j].t=(poss[j].t-ct)/(1-ct); + } else { + poss[j].piece+=3; + } + } + + } else { + // decouper puis repasser + if ( cp > theBDI ) { + Geom::Point pcP,ncP; + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[cp]); + pcP=nData->p; + } + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[cp+1]); + ncP=nData->p; + } + Geom::Point knotP=0.5*(pcP+ncP); + + InsertBezierTo(knotP,theBD.nb-(cp-theBDI),cp+1); + { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[theBDI]); + nData->nb=cp-theBDI; + } + + // decalages dans le tableau des positions de coupe + for (int j=curP;j<nbPos;j++) { + if ( poss[j].piece == cp ) { + poss[j].piece+=1; + } else { + poss[j].piece+=1; + } + } + curP--; + } else { + Geom::Point pcP,ncP; + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[cp+1]); + pcP=nData->p; + } + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[cp+2]); + ncP=nData->p; + } + Geom::Point knotP=0.5*(pcP+ncP); + + InsertBezierTo(knotP,theBD.nb-1,cp+2); + { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[theBDI]); + nData->nb=1; + } + + // decalages dans le tableau des positions de coupe + for (int j=curP;j<nbPos;j++) { + if ( poss[j].piece == cp ) { +// poss[j].piece+=1; + } else { + poss[j].piece+=1; + } + } + curP--; + } + } + } else { + // on laisse aussi tomber + } + } else { + // on laisse tomber + } + } + } +} + +void Path::ConvertPositionsToMoveTo(int nbPos,cut_position* poss) +{ + ConvertPositionsToForced(nbPos,poss); +// ConvertForcedToMoveTo(); + // on fait une version customizee a la place + + Path* res=new Path; + + Geom::Point lastP(0,0); + for (int i=0;i<int(descr_cmd.size());i++) { + int const typ = descr_cmd[i]->getType(); + if ( typ == descr_moveto ) { + Geom::Point np; + { + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]); + np=nData->p; + } + Geom::Point endP; + bool hasClose=false; + int hasForced=-1; + bool doesClose=false; + int j=i+1; + for (;j<int(descr_cmd.size());j++) { + int const ntyp = descr_cmd[j]->getType(); + if ( ntyp == descr_moveto ) { + j--; + break; + } else if ( ntyp == descr_forced ) { + if ( hasForced < 0 ) hasForced=j; + } else if ( ntyp == descr_close ) { + hasClose=true; + break; + } else if ( ntyp == descr_lineto ) { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[j]); + endP=nData->p; + } else if ( ntyp == descr_arcto ) { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[j]); + endP=nData->p; + } else if ( ntyp == descr_cubicto ) { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[j]); + endP=nData->p; + } else if ( ntyp == descr_bezierto ) { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[j]); + endP=nData->p; + } else { + } + } + if ( Geom::LInfty(endP-np) < 0.00001 ) { + doesClose=true; + } + if ( ( doesClose || hasClose ) && hasForced >= 0 ) { + // printf("nasty i=%i j=%i frc=%i\n",i,j,hasForced); + // aghhh. + Geom::Point nMvtP=PrevPoint(hasForced); + res->MoveTo(nMvtP); + Geom::Point nLastP=nMvtP; + for (int k = hasForced + 1; k < j; k++) { + int ntyp=descr_cmd[k]->getType(); + if ( ntyp == descr_moveto ) { + // ne doit pas arriver + } else if ( ntyp == descr_forced ) { + res->MoveTo(nLastP); + } else if ( ntyp == descr_close ) { + // rien a faire ici; de plus il ne peut y en avoir qu'un + } else if ( ntyp == descr_lineto ) { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[k]); + res->LineTo(nData->p); + nLastP=nData->p; + } else if ( ntyp == descr_arcto ) { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[k]); + res->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise); + nLastP=nData->p; + } else if ( ntyp == descr_cubicto ) { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[k]); + res->CubicTo(nData->p,nData->start,nData->end); + nLastP=nData->p; + } else if ( ntyp == descr_bezierto ) { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[k]); + res->BezierTo(nData->p); + nLastP=nData->p; + } else if ( ntyp == descr_interm_bezier ) { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[k]); + res->IntermBezierTo(nData->p); + } else { + } + } + if ( doesClose == false ) res->LineTo(np); + nLastP=np; + for (int k=i+1;k<hasForced;k++) { + int ntyp=descr_cmd[k]->getType(); + if ( ntyp == descr_moveto ) { + // ne doit pas arriver + } else if ( ntyp == descr_forced ) { + res->MoveTo(nLastP); + } else if ( ntyp == descr_close ) { + // rien a faire ici; de plus il ne peut y en avoir qu'un + } else if ( ntyp == descr_lineto ) { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[k]); + res->LineTo(nData->p); + nLastP=nData->p; + } else if ( ntyp == descr_arcto ) { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[k]); + res->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise); + nLastP=nData->p; + } else if ( ntyp == descr_cubicto ) { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[k]); + res->CubicTo(nData->p,nData->start,nData->end); + nLastP=nData->p; + } else if ( ntyp == descr_bezierto ) { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[k]); + res->BezierTo(nData->p); + nLastP=nData->p; + } else if ( ntyp == descr_interm_bezier ) { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[k]); + res->IntermBezierTo(nData->p); + } else { + } + } + lastP=nMvtP; + i=j; + } else { + // regular, just move on + res->MoveTo(np); + lastP=np; + } + } else if ( typ == descr_close ) { + res->Close(); + } else if ( typ == descr_forced ) { + res->MoveTo(lastP); + } else if ( typ == descr_lineto ) { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]); + res->LineTo(nData->p); + lastP=nData->p; + } else if ( typ == descr_arcto ) { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]); + res->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise); + lastP=nData->p; + } else if ( typ == descr_cubicto ) { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]); + res->CubicTo(nData->p,nData->start,nData->end); + lastP=nData->p; + } else if ( typ == descr_bezierto ) { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]); + res->BezierTo(nData->p); + lastP=nData->p; + } else if ( typ == descr_interm_bezier ) { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]); + res->IntermBezierTo(nData->p); + } else { + } + } + + Copy(res); + delete res; + return; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/PathOutline.cpp b/src/livarot/PathOutline.cpp new file mode 100644 index 0000000..c93c040 --- /dev/null +++ b/src/livarot/PathOutline.cpp @@ -0,0 +1,1530 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "livarot/Path.h" +#include "livarot/path-description.h" + +/* + * the "outliner" + * takes a sequence of path commands and produces a set of commands that approximates the offset + * result is stored in dest (that paremeter is handed to all the subfunctions) + * not that the result is in general not mathematically correct; you can end up with unwanted holes in your + * beautiful offset. a better way is to do path->polyline->polygon->offset of polygon->polyline(=contours of the polygon)->path + * but computing offsets of the path is faster... + */ + +// outline of a path. +// computed by making 2 offsets, one of the "left" side of the path, one of the right side, and then glueing the two +// the left side has to be reversed to make a contour +void Path::Outline(Path *dest, double width, JoinType join, ButtType butt, double miter) +{ + if ( descr_flags & descr_adding_bezier ) { + CancelBezier(); + } + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } + if ( descr_cmd.size() <= 1 ) { + return; + } + if ( dest == nullptr ) { + return; + } + + dest->Reset(); + dest->SetBackData(false); + + outline_callbacks calls; + Geom::Point endButt; + Geom::Point endPos; + calls.cubicto = StdCubicTo; + calls.bezierto = StdBezierTo; + calls.arcto = StdArcTo; + + Path *rev = new Path; + + // we repeat the offset contour creation for each subpath + int curP = 0; + do { + int lastM = curP; + do { + curP++; + if (curP >= int(descr_cmd.size())) { + break; + } + int typ = descr_cmd[curP]->getType(); + if (typ == descr_moveto) { + break; + } + } while (curP < int(descr_cmd.size())); + + if (curP >= int(descr_cmd.size())) { + curP = descr_cmd.size(); + } + + if (curP > lastM + 1) { + // we have isolated a subpath, now we make a reversed version of it + // we do so by taking the subpath in the reverse and constructing a path as appropriate + // the construct is stored in "rev" + int curD = curP - 1; + Geom::Point curX; + Geom::Point nextX; + int firstTyp = descr_cmd[curD]->getType(); + bool const needClose = (firstTyp == descr_close); + while (curD > lastM && descr_cmd[curD]->getType() == descr_close) { + curD--; + } + + int realP = curD + 1; + if (curD > lastM) { + curX = PrevPoint(curD); + rev->Reset (); + rev->MoveTo(curX); + while (curD > lastM) { + int const typ = descr_cmd[curD]->getType(); + if (typ == descr_moveto) { + // rev->Close(); + curD--; + } else if (typ == descr_forced) { + // rev->Close(); + curD--; + } else if (typ == descr_lineto) { + nextX = PrevPoint (curD - 1); + rev->LineTo (nextX); + curX = nextX; + curD--; + } else if (typ == descr_cubicto) { + PathDescrCubicTo* nData = dynamic_cast<PathDescrCubicTo*>(descr_cmd[curD]); + nextX = PrevPoint (curD - 1); + Geom::Point isD=-nData->start; + Geom::Point ieD=-nData->end; + rev->CubicTo (nextX, ieD,isD); + curX = nextX; + curD--; + } else if (typ == descr_arcto) { + PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(descr_cmd[curD]); + nextX = PrevPoint (curD - 1); + rev->ArcTo (nextX, nData->rx,nData->ry,nData->angle,nData->large,!nData->clockwise); + curX = nextX; + curD--; + } else if (typ == descr_bezierto) { + nextX = PrevPoint (curD - 1); + rev->LineTo (nextX); + curX = nextX; + curD--; + } else if (typ == descr_interm_bezier) { + int nD = curD - 1; + while (nD > lastM && descr_cmd[nD]->getType() != descr_bezierto) nD--; + if ((descr_cmd[nD]->getType()) != descr_bezierto) { + // pas trouve le debut!? + // Not find the start?! + nextX = PrevPoint (nD); + rev->LineTo (nextX); + curX = nextX; + } else { + nextX = PrevPoint (nD - 1); + rev->BezierTo (nextX); + for (int i = curD; i > nD; i--) { + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[i]); + rev->IntermBezierTo (nData->p); + } + rev->EndBezierTo (); + curX = nextX; + } + curD = nD - 1; + } else { + curD--; + } + } + + // offset the paths and glue everything + // actual offseting is done in SubContractOutline() + if (needClose) { + rev->Close (); + rev->SubContractOutline (0, rev->descr_cmd.size(), + dest, calls, 0.0025 * width * width, width, + join, butt, miter, true, false, endPos, endButt); + SubContractOutline (lastM, realP + 1 - lastM, + dest, calls, 0.0025 * width * width, + width, join, butt, miter, true, false, endPos, endButt); + } else { + rev->SubContractOutline (0, rev->descr_cmd.size(), + dest, calls, 0.0025 * width * width, width, + join, butt, miter, false, false, endPos, endButt); + Geom::Point endNor=endButt.ccw(); + if (butt == butt_round) { + dest->ArcTo (endPos+width*endButt, width, width, 0.0, false, true); + dest->ArcTo (endPos+width*endNor, width, width, 0.0, false, true); + } else if (butt == butt_square) { + dest->LineTo (endPos-width*endNor+width*endButt); + dest->LineTo (endPos+width*endNor+width*endButt); + dest->LineTo (endPos+width*endNor); + } else if (butt == butt_pointy) { + dest->LineTo (endPos+width*endButt); + dest->LineTo (endPos+width*endNor); + } else { + dest->LineTo (endPos+width*endNor); + } + SubContractOutline (lastM, realP - lastM, + dest, calls, 0.0025 * width * width, width, join, butt, + miter, false, true, endPos, endButt); + + endNor=endButt.ccw(); + if (butt == butt_round) { + dest->ArcTo (endPos+width*endButt, width, width, 0.0, false, true); + dest->ArcTo (endPos+width*endNor, width, width, 0.0, false, true); + } else if (butt == butt_square) { + dest->LineTo (endPos-width*endNor+width*endButt); + dest->LineTo (endPos+width*endNor+width*endButt); + dest->LineTo (endPos+width*endNor); + } else if (butt == butt_pointy) { + dest->LineTo (endPos+width*endButt); + dest->LineTo (endPos+width*endNor); + } else { + dest->LineTo (endPos+width*endNor); + } + dest->Close (); + } + } // if (curD > lastM) + } // if (curP > lastM + 1) + + } while (curP < int(descr_cmd.size())); + + delete rev; +} + +// versions for outlining closed path: they only make one side of the offset contour +void +Path::OutsideOutline (Path * dest, double width, JoinType join, ButtType butt, + double miter) +{ + if (descr_flags & descr_adding_bezier) { + CancelBezier(); + } + if (descr_flags & descr_doing_subpath) { + CloseSubpath(); + } + if (int(descr_cmd.size()) <= 1) return; + if (dest == nullptr) return; + dest->Reset (); + dest->SetBackData (false); + + outline_callbacks calls; + Geom::Point endButt, endPos; + calls.cubicto = StdCubicTo; + calls.bezierto = StdBezierTo; + calls.arcto = StdArcTo; + SubContractOutline (0, descr_cmd.size(), + dest, calls, 0.0025 * width * width, width, join, butt, + miter, true, false, endPos, endButt); +} + +void +Path::InsideOutline (Path * dest, double width, JoinType join, ButtType butt, + double miter) +{ + if ( descr_flags & descr_adding_bezier ) { + CancelBezier(); + } + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } + if (int(descr_cmd.size()) <= 1) return; + if (dest == nullptr) return; + dest->Reset (); + dest->SetBackData (false); + + outline_callbacks calls; + Geom::Point endButt, endPos; + calls.cubicto = StdCubicTo; + calls.bezierto = StdBezierTo; + calls.arcto = StdArcTo; + + Path *rev = new Path; + + int curP = 0; + do { + int lastM = curP; + do { + curP++; + if (curP >= int(descr_cmd.size())) break; + int typ = descr_cmd[curP]->getType(); + if (typ == descr_moveto) break; + } while (curP < int(descr_cmd.size())); + if (curP >= int(descr_cmd.size())) curP = descr_cmd.size(); + if (curP > lastM + 1) { + // Otherwise there's only one point. (tr: or "only a point") + // [sinon il n'y a qu'un point] + int curD = curP - 1; + Geom::Point curX; + Geom::Point nextX; + while (curD > lastM && (descr_cmd[curD]->getType()) == descr_close) curD--; + if (curD > lastM) { + curX = PrevPoint (curD); + rev->Reset (); + rev->MoveTo (curX); + while (curD > lastM) { + int typ = descr_cmd[curD]->getType(); + if (typ == descr_moveto) { + rev->Close (); + curD--; + } else if (typ == descr_forced) { + curD--; + } else if (typ == descr_lineto) { + nextX = PrevPoint (curD - 1); + rev->LineTo (nextX); + curX = nextX; + curD--; + } else if (typ == descr_cubicto) { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo*>(descr_cmd[curD]); + nextX = PrevPoint (curD - 1); + Geom::Point isD=-nData->start; + Geom::Point ieD=-nData->end; + rev->CubicTo (nextX, ieD,isD); + curX = nextX; + curD--; + } else if (typ == descr_arcto) { + PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(descr_cmd[curD]); + nextX = PrevPoint (curD - 1); + rev->ArcTo (nextX, nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise); + curX = nextX; + curD--; + } else if (typ == descr_bezierto) { + nextX = PrevPoint (curD - 1); + rev->LineTo (nextX); + curX = nextX; + curD--; + } else if (typ == descr_interm_bezier) { + int nD = curD - 1; + while (nD > lastM && (descr_cmd[nD]->getType()) != descr_bezierto) nD--; + if (descr_cmd[nD]->getType() != descr_bezierto) { + // pas trouve le debut!? + nextX = PrevPoint (nD); + rev->LineTo (nextX); + curX = nextX; + } else { + nextX = PrevPoint (nD - 1); + rev->BezierTo (nextX); + for (int i = curD; i > nD; i--) { + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[i]); + rev->IntermBezierTo (nData->p); + } + rev->EndBezierTo (); + curX = nextX; + } + curD = nD - 1; + } else { + curD--; + } + } + rev->Close (); + rev->SubContractOutline (0, rev->descr_cmd.size(), + dest, calls, 0.0025 * width * width, + width, join, butt, miter, true, false, + endPos, endButt); + } + } + } while (curP < int(descr_cmd.size())); + + delete rev; +} + + +// the offset +// take each command and offset it. +// the bezier spline is split in a sequence of bezier curves, and these are transformed in cubic bezier (which is +// not hard since they are quadratic bezier) +// joins are put where needed +void Path::SubContractOutline(int off, int num_pd, + Path *dest, outline_callbacks & calls, + double tolerance, double width, JoinType join, + ButtType /*butt*/, double miter, bool closeIfNeeded, + bool skipMoveto, Geom::Point &lastP, Geom::Point &lastT) +{ + outline_callback_data callsData; + + callsData.orig = this; + callsData.dest = dest; + int curP = 1; + + // le moveto + Geom::Point curX; + { + int firstTyp = descr_cmd[off]->getType(); + if ( firstTyp != descr_moveto ) { + curX[0] = curX[1] = 0; + curP = 0; + } else { + PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo*>(descr_cmd[off]); + curX = nData->p; + } + } + Geom::Point curT(0, 0); + + bool doFirst = true; + Geom::Point firstP(0, 0); + Geom::Point firstT(0, 0); + + // et le reste, 1 par 1 + while (curP < num_pd) + { + int curD = off + curP; + int nType = descr_cmd[curD]->getType(); + Geom::Point nextX; + Geom::Point stPos, enPos, stTgt, enTgt, stNor, enNor; + double stRad, enRad, stTle, enTle; + if (nType == descr_forced) { + curP++; + } else if (nType == descr_moveto) { + PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo*>(descr_cmd[curD]); + nextX = nData->p; + // et on avance + if (doFirst) { + } else { + if (closeIfNeeded) { + if ( Geom::LInfty (curX- firstP) < 0.0001 ) { + OutlineJoin (dest, firstP, curT, firstT, width, join, + miter, nType); + dest->Close (); + } else { + PathDescrLineTo temp(firstP); + + TangentOnSegAt (0.0, curX, temp, stPos, stTgt, + stTle); + TangentOnSegAt (1.0, curX, temp, enPos, enTgt, + enTle); + stNor=stTgt.cw(); + enNor=enTgt.cw(); + + // jointure + { + Geom::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, + miter, nType); + } + dest->LineTo (enPos+width*enNor); + + // jointure + { + Geom::Point pos; + pos = firstP; + OutlineJoin (dest, enPos, enNor, firstT, width, join, + miter, nType); + dest->Close (); + } + } + } + } + firstP = nextX; + curP++; + } + else if (nType == descr_close) + { + if (! doFirst) + { + if (Geom::LInfty (curX - firstP) < 0.0001) + { + OutlineJoin (dest, firstP, curT, firstT, width, join, + miter, nType); + dest->Close (); + } + else + { + PathDescrLineTo temp(firstP); + nextX = firstP; + + TangentOnSegAt (0.0, curX, temp, stPos, stTgt, stTle); + TangentOnSegAt (1.0, curX, temp, enPos, enTgt, enTle); + stNor=stTgt.cw(); + enNor=enTgt.cw(); + + // jointure + { + OutlineJoin (dest, stPos, curT, stNor, width, join, + miter, nType); + } + + dest->LineTo (enPos+width*enNor); + + // jointure + { + OutlineJoin (dest, enPos, enNor, firstT, width, join, + miter, nType); + dest->Close (); + } + } + } + doFirst = true; + curP++; + } + else if (nType == descr_lineto) + { + PathDescrLineTo* nData = dynamic_cast<PathDescrLineTo*>(descr_cmd[curD]); + nextX = nData->p; + // et on avance + TangentOnSegAt (0.0, curX, *nData, stPos, stTgt, stTle); + TangentOnSegAt (1.0, curX, *nData, enPos, enTgt, enTle); + // test de nullité du segment + if (IsNulCurve (descr_cmd, curD, curX)) + { + if (descr_cmd.size() == 2) { // single point, see LP Bug 1006666 + stTgt = dest->descr_cmd.size() ? Geom::Point(1, 0) : Geom::Point(-1, 0); // reverse direction + enTgt = stTgt; + } else { + curP++; + continue; + } + } + stNor=stTgt.cw(); + enNor=enTgt.cw(); + + lastP = enPos; + lastT = enTgt; + + if (doFirst) + { + doFirst = false; + firstP = stPos; + firstT = stNor; + if (skipMoveto) + { + skipMoveto = false; + } + else + dest->MoveTo (curX+width*stNor); + } + else + { + // jointure + Geom::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType); + } + + int n_d = dest->LineTo (nextX+width*enNor); + if (n_d >= 0) + { + dest->descr_cmd[n_d]->associated = curP; + dest->descr_cmd[n_d]->tSt = 0.0; + dest->descr_cmd[n_d]->tEn = 1.0; + } + curP++; + } + else if (nType == descr_cubicto) + { + PathDescrCubicTo* nData = dynamic_cast<PathDescrCubicTo*>(descr_cmd[curD]); + nextX = nData->p; + // test de nullite du segment + if (IsNulCurve (descr_cmd, curD, curX)) + { + curP++; + continue; + } + // et on avance + TangentOnCubAt (0.0, curX, *nData, false, stPos, stTgt, + stTle, stRad); + TangentOnCubAt (1.0, curX, *nData, true, enPos, enTgt, + enTle, enRad); + stNor=stTgt.cw(); + enNor=enTgt.cw(); + + lastP = enPos; + lastT = enTgt; + + if (doFirst) + { + doFirst = false; + firstP = stPos; + firstT = stNor; + if (skipMoveto) + { + skipMoveto = false; + } + else + dest->MoveTo (curX+width*stNor); + } + else + { + // jointure + Geom::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType); + } + + callsData.piece = curP; + callsData.tSt = 0.0; + callsData.tEn = 1.0; + callsData.x1 = curX[0]; + callsData.y1 = curX[1]; + callsData.x2 = nextX[0]; + callsData.y2 = nextX[1]; + callsData.d.c.dx1 = nData->start[0]; + callsData.d.c.dy1 = nData->start[1]; + callsData.d.c.dx2 = nData->end[0]; + callsData.d.c.dy2 = nData->end[1]; + (calls.cubicto) (&callsData, tolerance, width); + + curP++; + } + else if (nType == descr_arcto) + { + PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(descr_cmd[curD]); + nextX = nData->p; + // test de nullité du segment + if (IsNulCurve (descr_cmd, curD, curX)) + { + curP++; + continue; + } + // et on avance + TangentOnArcAt (0.0, curX, *nData, stPos, stTgt, stTle, + stRad); + TangentOnArcAt (1.0, curX, *nData, enPos, enTgt, enTle, + enRad); + stNor=stTgt.cw(); + enNor=enTgt.cw(); + + lastP = enPos; + lastT = enTgt; // tjs definie + + if (doFirst) + { + doFirst = false; + firstP = stPos; + firstT = stNor; + if (skipMoveto) + { + skipMoveto = false; + } + else + dest->MoveTo (curX+width*stNor); + } + else + { + // jointure + Geom::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType); + } + + callsData.piece = curP; + callsData.tSt = 0.0; + callsData.tEn = 1.0; + callsData.x1 = curX[0]; + callsData.y1 = curX[1]; + callsData.x2 = nextX[0]; + callsData.y2 = nextX[1]; + callsData.d.a.rx = nData->rx; + callsData.d.a.ry = nData->ry; + callsData.d.a.angle = nData->angle; + callsData.d.a.clock = nData->clockwise; + callsData.d.a.large = nData->large; + (calls.arcto) (&callsData, tolerance, width); + + curP++; + } + else if (nType == descr_bezierto) + { + PathDescrBezierTo* nBData = dynamic_cast<PathDescrBezierTo*>(descr_cmd[curD]); + int nbInterm = nBData->nb; + nextX = nBData->p; + + if (IsNulCurve (descr_cmd, curD, curX)) { + curP += nbInterm + 1; + continue; + } + + curP++; + + curD = off + curP; + int ip = curD; + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[ip]); + + if (nbInterm <= 0) { + // et on avance + PathDescrLineTo temp(nextX); + TangentOnSegAt (0.0, curX, temp, stPos, stTgt, stTle); + TangentOnSegAt (1.0, curX, temp, enPos, enTgt, enTle); + stNor=stTgt.cw(); + enNor=enTgt.cw(); + + lastP = enPos; + lastT = enTgt; + + if (doFirst) { + doFirst = false; + firstP = stPos; + firstT = stNor; + if (skipMoveto) { + skipMoveto = false; + } else dest->MoveTo (curX+width*stNor); + } else { + // jointure + Geom::Point pos; + pos = curX; + if (stTle > 0) OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType); + } + int n_d = dest->LineTo (nextX+width*enNor); + if (n_d >= 0) { + dest->descr_cmd[n_d]->associated = curP - 1; + dest->descr_cmd[n_d]->tSt = 0.0; + dest->descr_cmd[n_d]->tEn = 1.0; + } + } else if (nbInterm == 1) { + Geom::Point midX; + midX = nData->p; + // et on avance + TangentOnBezAt (0.0, curX, *nData, *nBData, false, stPos, stTgt, stTle, stRad); + TangentOnBezAt (1.0, curX, *nData, *nBData, true, enPos, enTgt, enTle, enRad); + stNor=stTgt.cw(); + enNor=enTgt.cw(); + + lastP = enPos; + lastT = enTgt; + + if (doFirst) { + doFirst = false; + firstP = stPos; + firstT = stNor; + if (skipMoveto) { + skipMoveto = false; + } else dest->MoveTo (curX+width*stNor); + } else { + // jointure + Geom::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType); + } + + callsData.piece = curP; + callsData.tSt = 0.0; + callsData.tEn = 1.0; + callsData.x1 = curX[0]; + callsData.y1 = curX[1]; + callsData.x2 = nextX[0]; + callsData.y2 = nextX[1]; + callsData.d.b.mx = midX[0]; + callsData.d.b.my = midX[1]; + (calls.bezierto) (&callsData, tolerance, width); + + } else if (nbInterm > 1) { + Geom::Point bx=curX; + Geom::Point cx=curX; + Geom::Point dx=nData->p; + + TangentOnBezAt (0.0, curX, *nData, *nBData, false, stPos, stTgt, stTle, stRad); + stNor=stTgt.cw(); + + ip++; + nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[ip]); + // et on avance + if (stTle > 0) { + if (doFirst) { + doFirst = false; + firstP = stPos; + firstT = stNor; + if (skipMoveto) { + skipMoveto = false; + } else dest->MoveTo (curX+width*stNor); + } else { + // jointure + Geom::Point pos=curX; + OutlineJoin (dest, pos, stTgt, stNor, width, join, miter, nType); + // dest->LineTo(curX+width*stNor.x,curY+width*stNor.y); + } + } + + cx = 2 * bx - dx; + + for (int k = 0; k < nbInterm - 1; k++) { + bx = cx; + cx = dx; + + dx = nData->p; + ip++; + nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[ip]); + Geom::Point stx = (bx + cx) / 2; + // double stw=(bw+cw)/2; + + PathDescrBezierTo tempb((cx + dx) / 2, 1); + PathDescrIntermBezierTo tempi(cx); + TangentOnBezAt (1.0, stx, tempi, tempb, true, enPos, enTgt, enTle, enRad); + enNor=enTgt.cw(); + + lastP = enPos; + lastT = enTgt; + + callsData.piece = curP + k; + callsData.tSt = 0.0; + callsData.tEn = 1.0; + callsData.x1 = stx[0]; + callsData.y1 = stx[1]; + callsData.x2 = (cx[0] + dx[0]) / 2; + callsData.y2 = (cx[1] + dx[1]) / 2; + callsData.d.b.mx = cx[0]; + callsData.d.b.my = cx[1]; + (calls.bezierto) (&callsData, tolerance, width); + } + { + bx = cx; + cx = dx; + + dx = nextX; + dx = 2 * dx - cx; + + Geom::Point stx = (bx + cx) / 2; + // double stw=(bw+cw)/2; + + PathDescrBezierTo tempb((cx + dx) / 2, 1); + PathDescrIntermBezierTo tempi(cx); + TangentOnBezAt (1.0, stx, tempi, tempb, true, enPos, + enTgt, enTle, enRad); + enNor=enTgt.cw(); + + lastP = enPos; + lastT = enTgt; + + callsData.piece = curP + nbInterm - 1; + callsData.tSt = 0.0; + callsData.tEn = 1.0; + callsData.x1 = stx[0]; + callsData.y1 = stx[1]; + callsData.x2 = (cx[0] + dx[0]) / 2; + callsData.y2 = (cx[1] + dx[1]) / 2; + callsData.d.b.mx = cx[0]; + callsData.d.b.my = cx[1]; + (calls.bezierto) (&callsData, tolerance, width); + + } + } + + // et on avance + curP += nbInterm; + } + curX = nextX; + curT = enNor; // sera tjs bien definie + } + if (closeIfNeeded) + { + if (! doFirst) + { + } + } + +} + +/* + * + * utilitaires pour l'outline + * + */ + +// like the name says: check whether the path command is actually more than a dumb point. +bool +Path::IsNulCurve (std::vector<PathDescr*> const &cmd, int curD, Geom::Point const &curX) +{ + switch(cmd[curD]->getType()) { + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo*>(cmd[curD]); + if (Geom::LInfty(nData->p - curX) < 0.00001) { + return true; + } + return false; + } + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo*>(cmd[curD]); + Geom::Point A = nData->start + nData->end + 2*(curX - nData->p); + Geom::Point B = 3*(nData->p - curX) - 2*nData->start - nData->end; + Geom::Point C = nData->start; + if (Geom::LInfty(A) < 0.0001 + && Geom::LInfty(B) < 0.0001 + && Geom::LInfty (C) < 0.0001) { + return true; + } + return false; + } + case descr_arcto: + { + PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(cmd[curD]); + if ( Geom::LInfty(nData->p - curX) < 0.00001) { + if ((! nData->large) + || (fabs (nData->rx) < 0.00001 + || fabs (nData->ry) < 0.00001)) { + return true; + } + } + return false; + } + case descr_bezierto: + { + PathDescrBezierTo* nBData = dynamic_cast<PathDescrBezierTo*>(cmd[curD]); + if (nBData->nb <= 0) + { + if (Geom::LInfty(nBData->p - curX) < 0.00001) { + return true; + } + return false; + } + else if (nBData->nb == 1) + { + if (Geom::LInfty(nBData->p - curX) < 0.00001) { + int ip = curD + 1; + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(cmd[ip]); + if (Geom::LInfty(nData->p - curX) < 0.00001) { + return true; + } + } + return false; + } else if (Geom::LInfty(nBData->p - curX) < 0.00001) { + for (int i = 1; i <= nBData->nb; i++) { + int ip = curD + i; + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(cmd[ip]); + if (Geom::LInfty(nData->p - curX) > 0.00001) { + return false; + } + } + return true; + } + } + default: + return true; + } +} + +// tangents and curvarture computing, for the different path command types. +// the need for tangent is obvious: it gives the normal, along which we offset points +// curvature is used to do strength correction on the length of the tangents to the offset (see +// cubic offset) + +/** + * \param at Distance along a tangent (0 <= at <= 1). + * \param iS Start point. + * \param fin LineTo description containing end point. + * \param pos Filled in with the position of `at' on the segment. + * \param tgt Filled in with the normalised tangent vector. + * \param len Filled in with the length of the segment. + */ + +void Path::TangentOnSegAt(double at, Geom::Point const &iS, PathDescrLineTo const &fin, + Geom::Point &pos, Geom::Point &tgt, double &len) +{ + Geom::Point const iE = fin.p; + Geom::Point const seg = iE - iS; + double const l = L2(seg); + if (l <= 0.000001) { + pos = iS; + tgt = Geom::Point(0, 0); + len = 0; + } else { + tgt = seg / l; + pos = (1 - at) * iS + at * iE; // in other words, pos = iS + at * seg + len = l; + } +} + +// barf +void Path::TangentOnArcAt(double at, const Geom::Point &iS, PathDescrArcTo const &fin, + Geom::Point &pos, Geom::Point &tgt, double &len, double &rad) +{ + Geom::Point const iE = fin.p; + double const rx = fin.rx; + double const ry = fin.ry; + double const angle = fin.angle*M_PI/180.0; + bool const large = fin.large; + bool const wise = fin.clockwise; + + pos = iS; + tgt[0] = tgt[1] = 0; + if (rx <= 0.0001 || ry <= 0.0001) + return; + + double const sex = iE[0] - iS[0], sey = iE[1] - iS[1]; + double const ca = cos (angle), sa = sin (angle); + double csex = ca * sex + sa * sey; + double csey = -sa * sex + ca * sey; + csex /= rx; + csey /= ry; + double l = csex * csex + csey * csey; + double const d = sqrt(std::max(1 - l / 4, 0.0)); + double csdx = csey; + double csdy = -csex; + l = sqrt(l); + csdx /= l; + csdy /= l; + csdx *= d; + csdy *= d; + + double sang; + double eang; + double rax = -csdx - csex / 2; + double ray = -csdy - csey / 2; + if (rax < -1) + { + sang = M_PI; + } + else if (rax > 1) + { + sang = 0; + } + else + { + sang = acos (rax); + if (ray < 0) + sang = 2 * M_PI - sang; + } + rax = -csdx + csex / 2; + ray = -csdy + csey / 2; + if (rax < -1) + { + eang = M_PI; + } + else if (rax > 1) + { + eang = 0; + } + else + { + eang = acos (rax); + if (ray < 0) + eang = 2 * M_PI - eang; + } + + csdx *= rx; + csdy *= ry; + double drx = ca * csdx - sa * csdy; + double dry = sa * csdx + ca * csdy; + + if (wise) + { + if (large) + { + drx = -drx; + dry = -dry; + double swap = eang; + eang = sang; + sang = swap; + eang += M_PI; + sang += M_PI; + if (eang >= 2 * M_PI) + eang -= 2 * M_PI; + if (sang >= 2 * M_PI) + sang -= 2 * M_PI; + } + } + else + { + if (! large) + { + drx = -drx; + dry = -dry; + double swap = eang; + eang = sang; + sang = swap; + eang += M_PI; + sang += M_PI; + if (eang >= 2 * M_PI) + eang -= 2 * M_PI; + if (sang >= 2 * M_PI) + sang -= 2 * M_PI; + } + } + drx += (iS[0] + iE[0]) / 2; + dry += (iS[1] + iE[1]) / 2; + + if (wise) { + if (sang < eang) + sang += 2 * M_PI; + double b = sang * (1 - at) + eang * at; + double cb = cos (b), sb = sin (b); + pos[0] = drx + ca * rx * cb - sa * ry * sb; + pos[1] = dry + sa * rx * cb + ca * ry * sb; + tgt[0] = ca * rx * sb + sa * ry * cb; + tgt[1] = sa * rx * sb - ca * ry * cb; + Geom::Point dtgt; + dtgt[0] = -ca * rx * cb + sa * ry * sb; + dtgt[1] = -sa * rx * cb - ca * ry * sb; + len = L2(tgt); + rad = -len * dot(tgt, tgt) / (tgt[0] * dtgt[1] - tgt[1] * dtgt[0]); + tgt /= len; + } + else + { + if (sang > eang) + sang -= 2 * M_PI; + double b = sang * (1 - at) + eang * at; + double cb = cos (b), sb = sin (b); + pos[0] = drx + ca * rx * cb - sa * ry * sb; + pos[1] = dry + sa * rx * cb + ca * ry * sb; + tgt[0] = ca * rx * sb + sa * ry * cb; + tgt[1] = sa * rx * sb - ca * ry * cb; + Geom::Point dtgt; + dtgt[0] = -ca * rx * cb + sa * ry * sb; + dtgt[1] = -sa * rx * cb - ca * ry * sb; + len = L2(tgt); + rad = len * dot(tgt, tgt) / (tgt[0] * dtgt[1] - tgt[1] * dtgt[0]); + tgt /= len; + } + + if (!wise) { + tgt = -tgt; + } +} +void +Path::TangentOnCubAt (double at, Geom::Point const &iS, PathDescrCubicTo const &fin, bool before, + Geom::Point &pos, Geom::Point &tgt, double &len, double &rad) +{ + const Geom::Point E = fin.p; + const Geom::Point Sd = fin.start; + const Geom::Point Ed = fin.end; + + pos = iS; + tgt = Geom::Point(0,0); + len = rad = 0; + + const Geom::Point A = Sd + Ed - 2*E + 2*iS; + const Geom::Point B = 0.5*(Ed - Sd); + const Geom::Point C = 0.25*(6*E - 6*iS - Sd - Ed); + const Geom::Point D = 0.125*(4*iS + 4*E - Ed + Sd); + const double atb = at - 0.5; + pos = (atb * atb * atb)*A + (atb * atb)*B + atb*C + D; + const Geom::Point der = (3 * atb * atb)*A + (2 * atb)*B + C; + const Geom::Point dder = (6 * atb)*A + 2*B; + const Geom::Point ddder = 6 * A; + + double l = Geom::L2 (der); + // lots of nasty cases. inversion points are sadly too common... + if (l <= 0.0001) { + len = 0; + l = L2(dder); + if (l <= 0.0001) { + l = L2(ddder); + if (l <= 0.0001) { + // pas de segment.... + return; + } + rad = 100000000; + tgt = ddder / l; + if (before) { + tgt = -tgt; + } + return; + } + rad = -l * (dot(dder,dder)) / (cross(dder, ddder)); + tgt = dder / l; + if (before) { + tgt = -tgt; + } + return; + } + len = l; + + rad = -l * (dot(der,der)) / (cross(der, dder)); + + tgt = der / l; +} + +void +Path::TangentOnBezAt (double at, Geom::Point const &iS, + PathDescrIntermBezierTo & mid, + PathDescrBezierTo & fin, bool before, Geom::Point & pos, + Geom::Point & tgt, double &len, double &rad) +{ + pos = iS; + tgt = Geom::Point(0,0); + len = rad = 0; + + const Geom::Point A = fin.p + iS - 2*mid.p; + const Geom::Point B = 2*mid.p - 2 * iS; + const Geom::Point C = iS; + + pos = at * at * A + at * B + C; + const Geom::Point der = 2 * at * A + B; + const Geom::Point dder = 2 * A; + double l = Geom::L2(der); + + if (l <= 0.0001) { + l = Geom::L2(dder); + if (l <= 0.0001) { + // pas de segment.... + // Not a segment. + return; + } + rad = 100000000; // Why this number? + tgt = dder / l; + if (before) { + tgt = -tgt; + } + return; + } + len = l; + rad = -l * (dot(der,der)) / (cross(der, dder)); + + tgt = der / l; +} + +void +Path::OutlineJoin (Path * dest, Geom::Point pos, Geom::Point stNor, Geom::Point enNor, double width, + JoinType join, double miter, int nType) +{ + /* + Arbitrarily decide if we're on the inside or outside of a half turn. + A turn of 180 degrees (line path leaves the node in the same direction as it arrived) + is symmetric and has no real inside and outside. However when outlining we shall handle + one path as inside and the reverse path as outside. Handling both as inside joins (as + was done previously) will cut off round joins. Handling both as outside joins could + ideally work because both should fall together, but it seems that this causes many + extra nodes (due to rounding errors). Solution: for the 'half turn'-case toggle + inside/outside each time the same node is processed 2 consecutive times. + */ + static bool TurnInside = true; + static Geom::Point PrevPos(0, 0); + TurnInside ^= PrevPos == pos; + PrevPos = pos; + + const double angSi = cross (stNor, enNor); + const double angCo = dot (stNor, enNor); + + if ((fabs(angSi) < .0000001) && angCo > 0) { // The join is straight -> nothing to do. + } else { + if ((angSi > 0 && width >= 0) + || (angSi < 0 && width < 0)) { // This is an inside join -> join is independent of chosen JoinType. + if ((dest->descr_cmd[dest->descr_cmd.size() - 1]->getType() == descr_lineto) && (nType == descr_lineto)) { + Geom::Point const biss = unit_vector(Geom::rot90( stNor - enNor )); + double c2 = Geom::dot (biss, enNor); + if (fabs(c2) > M_SQRT1_2) { // apply only to obtuse angles + double l = width / c2; + PathDescrLineTo* nLine = dynamic_cast<PathDescrLineTo*>(dest->descr_cmd[dest->descr_cmd.size() - 1]); + nLine->p = pos + l*biss; // relocate to bisector + } else { + dest->LineTo (pos + width*enNor); + } + } else { +// dest->LineTo (pos); // redundant + dest->LineTo (pos + width*enNor); + } + } else if (angSi == 0 && TurnInside) { // Half turn (180 degrees) ... inside (see above). + dest->LineTo (pos + width*enNor); + } else { // This is an outside join -> chosen JoinType should be applied. + if (join == join_round) { + // Use the ends of the cubic: approximate the arc at the + // point where .., and support better the rounding of + // coordinates of the end points. + + // utiliser des bouts de cubique: approximation de l'arc (au point ou on en est...), et supporte mieux + // l'arrondi des coordonnees des extremites + /* double angle=acos(angCo); + if ( angCo >= 0 ) { + Geom::Point stTgt,enTgt; + RotCCWTo(stNor,stTgt); + RotCCWTo(enNor,enTgt); + dest->CubicTo(pos.x+width*enNor.x,pos.y+width*enNor.y, + angle*width*stTgt.x,angle*width*stTgt.y, + angle*width*enTgt.x,angle*width*enTgt.y); + } else { + Geom::Point biNor; + Geom::Point stTgt,enTgt,biTgt; + biNor.x=stNor.x+enNor.x; + biNor.y=stNor.y+enNor.y; + double biL=sqrt(biNor.x*biNor.x+biNor.y*biNor.y); + biNor.x/=biL; + biNor.y/=biL; + RotCCWTo(stNor,stTgt); + RotCCWTo(enNor,enTgt); + RotCCWTo(biNor,biTgt); + dest->CubicTo(pos.x+width*biNor.x,pos.y+width*biNor.y, + angle*width*stTgt.x,angle*width*stTgt.y, + angle*width*biTgt.x,angle*width*biTgt.y); + dest->CubicTo(pos.x+width*enNor.x,pos.y+width*enNor.y, + angle*width*biTgt.x,angle*width*biTgt.y, + angle*width*enTgt.x,angle*width*enTgt.y); + }*/ + if (width > 0) { + dest->ArcTo (pos + width*enNor, + 1.0001 * width, 1.0001 * width, 0.0, false, true); + } else { + dest->ArcTo (pos + width*enNor, + -1.0001 * width, -1.0001 * width, 0.0, false, + false); + } + } else if (join == join_pointy) { + Geom::Point const biss = unit_vector(Geom::rot90( stNor - enNor )); + double c2 = Geom::dot (biss, enNor); + double l = width / c2; + if ( fabs(l) > miter) { + dest->LineTo (pos + width*enNor); + } else { + if (dest->descr_cmd[dest->descr_cmd.size() - 1]->getType() == descr_lineto) { + PathDescrLineTo* nLine = dynamic_cast<PathDescrLineTo*>(dest->descr_cmd[dest->descr_cmd.size() - 1]); + nLine->p = pos+l*biss; // relocate to bisector + } else { + dest->LineTo (pos+l*biss); + } + if (nType != descr_lineto) + dest->LineTo (pos+width*enNor); + } + } else { // Bevel join + dest->LineTo (pos + width*enNor); + } + } + } +} + +// les callbacks + +// see http://www.home.unix-ag.org/simon/sketch/pathstroke.py to understand what's happening here + +void +Path::RecStdCubicTo (outline_callback_data * data, double tol, double width, + int lev) +{ + Geom::Point stPos, miPos, enPos; + Geom::Point stTgt, enTgt, miTgt, stNor, enNor, miNor; + double stRad, miRad, enRad; + double stTle, miTle, enTle; + // un cubic + { + PathDescrCubicTo temp(Geom::Point(data->x2, data->y2), + Geom::Point(data->d.c.dx1, data->d.c.dy1), + Geom::Point(data->d.c.dx2, data->d.c.dy2)); + + Geom::Point initial_point(data->x1, data->y1); + TangentOnCubAt (0.0, initial_point, temp, false, stPos, stTgt, stTle, + stRad); + TangentOnCubAt (0.5, initial_point, temp, false, miPos, miTgt, miTle, + miRad); + TangentOnCubAt (1.0, initial_point, temp, true, enPos, enTgt, enTle, + enRad); + stNor=stTgt.cw(); + miNor=miTgt.cw(); + enNor=enTgt.cw(); + } + + double stGue = 1, miGue = 1, enGue = 1; + // correction of the lengths of the tangent to the offset + // if you don't see why i wrote that, draw a little figure and everything will be clear + if (fabs (stRad) > 0.01) + stGue += width / stRad; + if (fabs (miRad) > 0.01) + miGue += width / miRad; + if (fabs (enRad) > 0.01) + enGue += width / enRad; + stGue *= stTle; + miGue *= miTle; + enGue *= enTle; + + + if (lev <= 0) { + int n_d = data->dest->CubicTo (enPos + width*enNor, + stGue*stTgt, + enGue*enTgt); + if (n_d >= 0) { + data->dest->descr_cmd[n_d]->associated = data->piece; + data->dest->descr_cmd[n_d]->tSt = data->tSt; + data->dest->descr_cmd[n_d]->tEn = data->tEn; + } + return; + } + + Geom::Point chk; + const Geom::Point req = miPos + width * miNor; + { + PathDescrCubicTo temp(enPos + width * enNor, + stGue * stTgt, + enGue * enTgt); + double chTle, chRad; + Geom::Point chTgt; + TangentOnCubAt (0.5, stPos+width*stNor, + temp, false, chk, chTgt, chTle, chRad); + } + const Geom::Point diff = req - chk; + const double err = dot(diff,diff); + if (err <= tol ) { // tolerance is given as a quadratic value, no need to use tol*tol here +// printf("%f <= %f %i\n",err,tol,lev); + int n_d = data->dest->CubicTo (enPos + width*enNor, + stGue*stTgt, + enGue*enTgt); + if (n_d >= 0) { + data->dest->descr_cmd[n_d]->associated = data->piece; + data->dest->descr_cmd[n_d]->tSt = data->tSt; + data->dest->descr_cmd[n_d]->tEn = data->tEn; + } + } else { + outline_callback_data desc = *data; + + desc.tSt = data->tSt; + desc.tEn = (data->tSt + data->tEn) / 2; + desc.x1 = data->x1; + desc.y1 = data->y1; + desc.x2 = miPos[0]; + desc.y2 = miPos[1]; + desc.d.c.dx1 = 0.5 * stTle * stTgt[0]; + desc.d.c.dy1 = 0.5 * stTle * stTgt[1]; + desc.d.c.dx2 = 0.5 * miTle * miTgt[0]; + desc.d.c.dy2 = 0.5 * miTle * miTgt[1]; + RecStdCubicTo (&desc, tol, width, lev - 1); + + desc.tSt = (data->tSt + data->tEn) / 2; + desc.tEn = data->tEn; + desc.x1 = miPos[0]; + desc.y1 = miPos[1]; + desc.x2 = data->x2; + desc.y2 = data->y2; + desc.d.c.dx1 = 0.5 * miTle * miTgt[0]; + desc.d.c.dy1 = 0.5 * miTle * miTgt[1]; + desc.d.c.dx2 = 0.5 * enTle * enTgt[0]; + desc.d.c.dy2 = 0.5 * enTle * enTgt[1]; + RecStdCubicTo (&desc, tol, width, lev - 1); + } +} + +void +Path::StdCubicTo (Path::outline_callback_data * data, double tol, double width) +{ +// fflush (stdout); + RecStdCubicTo (data, tol, width, 8); +} + +void +Path::StdBezierTo (Path::outline_callback_data * data, double tol, double width) +{ + PathDescrBezierTo tempb(Geom::Point(data->x2, data->y2), 1); + PathDescrIntermBezierTo tempi(Geom::Point(data->d.b.mx, data->d.b.my)); + Geom::Point stPos, enPos, stTgt, enTgt; + double stRad, enRad, stTle, enTle; + Geom::Point tmp(data->x1,data->y1); + TangentOnBezAt (0.0, tmp, tempi, tempb, false, stPos, stTgt, + stTle, stRad); + TangentOnBezAt (1.0, tmp, tempi, tempb, true, enPos, enTgt, + enTle, enRad); + data->d.c.dx1 = stTle * stTgt[0]; + data->d.c.dy1 = stTle * stTgt[1]; + data->d.c.dx2 = enTle * enTgt[0]; + data->d.c.dy2 = enTle * enTgt[1]; + RecStdCubicTo (data, tol, width, 8); +} + +void +Path::RecStdArcTo (outline_callback_data * data, double tol, double width, + int lev) +{ + Geom::Point stPos, miPos, enPos; + Geom::Point stTgt, enTgt, miTgt, stNor, enNor, miNor; + double stRad, miRad, enRad; + double stTle, miTle, enTle; + // un cubic + { + PathDescrArcTo temp(Geom::Point(data->x2, data->y2), + data->d.a.rx, data->d.a.ry, + data->d.a.angle, data->d.a.large, data->d.a.clock); + + Geom::Point tmp(data->x1,data->y1); + TangentOnArcAt (data->d.a.stA, tmp, temp, stPos, stTgt, + stTle, stRad); + TangentOnArcAt ((data->d.a.stA + data->d.a.enA) / 2, tmp, + temp, miPos, miTgt, miTle, miRad); + TangentOnArcAt (data->d.a.enA, tmp, temp, enPos, enTgt, + enTle, enRad); + stNor=stTgt.cw(); + miNor=miTgt.cw(); + enNor=enTgt.cw(); + } + + double stGue = 1, miGue = 1, enGue = 1; + if (fabs (stRad) > 0.01) + stGue += width / stRad; + if (fabs (miRad) > 0.01) + miGue += width / miRad; + if (fabs (enRad) > 0.01) + enGue += width / enRad; + stGue *= stTle; + miGue *= miTle; + enGue *= enTle; + double sang, eang; + { + Geom::Point tms(data->x1,data->y1),tme(data->x2,data->y2); + ArcAngles (tms,tme, data->d.a.rx, + data->d.a.ry, data->d.a.angle*M_PI/180.0, data->d.a.large, !data->d.a.clock, + sang, eang); + } + double scal = eang - sang; + if (scal < 0) + scal += 2 * M_PI; + if (scal > 2 * M_PI) + scal -= 2 * M_PI; + scal *= data->d.a.enA - data->d.a.stA; + + if (lev <= 0) + { + int n_d = data->dest->CubicTo (enPos + width*enNor, + stGue*scal*stTgt, + enGue*scal*enTgt); + if (n_d >= 0) { + data->dest->descr_cmd[n_d]->associated = data->piece; + data->dest->descr_cmd[n_d]->tSt = data->d.a.stA; + data->dest->descr_cmd[n_d]->tEn = data->d.a.enA; + } + return; + } + + Geom::Point chk; + const Geom::Point req = miPos + width*miNor; + { + PathDescrCubicTo temp(enPos + width * enNor, stGue * scal * stTgt, enGue * scal * enTgt); + double chTle, chRad; + Geom::Point chTgt; + TangentOnCubAt (0.5, stPos+width*stNor, + temp, false, chk, chTgt, chTle, chRad); + } + const Geom::Point diff = req - chk; + const double err = (dot(diff,diff)); + if (err <= tol) // tolerance is given as a quadratic value, no need to use tol*tol here + { + int n_d = data->dest->CubicTo (enPos + width*enNor, + stGue*scal*stTgt, + enGue*scal*enTgt); + if (n_d >= 0) { + data->dest->descr_cmd[n_d]->associated = data->piece; + data->dest->descr_cmd[n_d]->tSt = data->d.a.stA; + data->dest->descr_cmd[n_d]->tEn = data->d.a.enA; + } + } else { + outline_callback_data desc = *data; + + desc.d.a.stA = data->d.a.stA; + desc.d.a.enA = (data->d.a.stA + data->d.a.enA) / 2; + RecStdArcTo (&desc, tol, width, lev - 1); + + desc.d.a.stA = (data->d.a.stA + data->d.a.enA) / 2; + desc.d.a.enA = data->d.a.enA; + RecStdArcTo (&desc, tol, width, lev - 1); + } +} + +void +Path::StdArcTo (Path::outline_callback_data * data, double tol, double width) +{ + data->d.a.stA = 0.0; + data->d.a.enA = 1.0; + RecStdArcTo (data, tol, width, 8); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/livarot/PathSimplify.cpp b/src/livarot/PathSimplify.cpp new file mode 100644 index 0000000..4317783 --- /dev/null +++ b/src/livarot/PathSimplify.cpp @@ -0,0 +1,1550 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <memory> +#include <glib.h> +#include <2geom/affine.h> +#include "livarot/Path.h" +#include "livarot/path-description.h" + +/* + * Reassembling polyline segments into cubic bezier patches + * thes functions do not need the back data. but they are slower than recomposing + * path descriptions when you have said back data (it's always easier with a model) + * there's a bezier fitter in bezier-utils.cpp too. the main difference is the way bezier patch are split + * here: walk on the polyline, trying to extend the portion you can fit by respecting the treshhold, split when + * treshhold is exceeded. when encountering a "forced" point, lower the treshhold to favor splitting at that point + * in bezier-utils: fit the whole polyline, get the position with the higher deviation to the fitted curve, split + * there and recurse + */ + + +// algo d'origine: http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html + +// need the b-spline basis for cubic splines +// pas oublier que c'est une b-spline clampee +// et que ca correspond a une courbe de bezier normale +#define N03(t) ((1.0-t)*(1.0-t)*(1.0-t)) +#define N13(t) (3*t*(1.0-t)*(1.0-t)) +#define N23(t) (3*t*t*(1.0-t)) +#define N33(t) (t*t*t) +// quadratic b-splines (jsut in case) +#define N02(t) ((1.0-t)*(1.0-t)) +#define N12(t) (2*t*(1.0-t)) +#define N22(t) (t*t) +// linear interpolation b-splines +#define N01(t) ((1.0-t)) +#define N11(t) (t) + + + +void Path::Simplify(double treshhold) +{ + // There is nothing to fit if you have 0 to 1 points + if (pts.size() <= 1) { + return; + } + + // clear all existing path descriptions + Reset(); + + // each path (where a path is a MoveTo followed by one or more LineTo) is fitted on separately + // Say you had M L L L L M L L L M L L L L + // pattern -------- ------- --------- + // path 1 path 2 path 3 + // Each would be simplified individually. + + int lastM = 0; // index of the lastMove + while (lastM < int(pts.size())) { + int lastP = lastM + 1; // last point + while (lastP < int(pts.size()) + && (pts[lastP].isMoveTo == polyline_lineto + || pts[lastP].isMoveTo == polyline_forced)) // if it's a LineTo or a forcedPoint we move forward + { + lastP++; + } + // we would only come out of the above while loop when pts[lastP] becomes a MoveTo or we + // run out of points + // M L L L L L + // 0 1 2 3 4 5 6 <-- we came out from loop here + // lastM = 0; lastP = 6; lastP - lastM = 6; + // We pass the first point the algorithm should start at (lastM) and the total number of + // points (lastP - lastM) + DoSimplify(lastM, lastP - lastM, treshhold); + + lastM = lastP; + } +} + + +#if 0 +// dichomtomic method to get distance to curve approximation +// a real polynomial solver would get the minimum more efficiently, but since the polynom +// would likely be of degree >= 5, that would imply using some generic solver, liek using the sturm method +static double RecDistanceToCubic(Geom::Point const &iS, Geom::Point const &isD, + Geom::Point const &iE, Geom::Point const &ieD, + Geom::Point &pt, double current, int lev, double st, double et) +{ + if ( lev <= 0 ) { + return current; + } + + Geom::Point const m = 0.5 * (iS + iE) + 0.125 * (isD - ieD); + Geom::Point const md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + double const mt = (st + et) / 2; + + Geom::Point const hisD = 0.5 * isD; + Geom::Point const hieD = 0.5 * ieD; + + Geom::Point const mp = pt - m; + double nle = Geom::dot(mp, mp); + + if ( nle < current ) { + + current = nle; + nle = RecDistanceToCubic(iS, hisD, m, md, pt, current, lev - 1, st, mt); + if ( nle < current ) { + current = nle; + } + nle = RecDistanceToCubic(m, md, iE, hieD, pt, current, lev - 1, mt, et); + if ( nle < current ) { + current = nle; + } + + } else if ( nle < 2 * current ) { + + nle = RecDistanceToCubic(iS, hisD, m, md, pt, current, lev - 1, st, mt); + if ( nle < current ) { + current = nle; + } + nle = RecDistanceToCubic(m, md, iE, hieD, pt, current, lev - 1, mt, et); + if ( nle < current ) { + current = nle; + } + } + + return current; +} +#endif + +/** + * Smallest distance from a point to a line. + * + * You might think this function calculates the distance from a CubicBezier to a point? Neh, it + * doesn't do that. Firstly, this function doesn't care at all about a CubicBezier, as you can see + * res.start and res.end are not even used in the function body. It calculates the shortest + * possible distance to get from the point pt to the line from start to res.p. + * There two possibilities so let me illustrate: + * Case 1: + * + * o (pt) + * | + * | <------ returns this distance + * | + * o-------------------------o + * start res.p + * + * Case 2: + * + * o (pt) + * - + * - + * - <--- returns this distance + * - + * - + * o-------------------------o + * start res.p + * if the line is defined by l(t) over 0 <= t <= 1, this distance between pt and any point on line + * l(t) is defined as |l(t) - pt|. This function returns the minimum of this function over t. + * + * @param start The start point of the cubic Bezier. + * @param res The cubic Bezier command which we really don't care about. We only use res.p. + * @param pt The point to measure the distance from. + * + * @return See the comments above. + */ +static double DistanceToCubic(Geom::Point const &start, PathDescrCubicTo res, Geom::Point &pt) +{ + Geom::Point const sp = pt - start; + Geom::Point const ep = pt - res.p; + double nle = Geom::dot(sp, sp); + double nnle = Geom::dot(ep, ep); + if ( nnle < nle ) { + nle = nnle; + } + + Geom::Point seg = res.p - start; + nnle = Geom::cross(sp, seg); + nnle *= nnle; + nnle /= Geom::dot(seg, seg); + if ( nnle < nle ) { + if ( Geom::dot(sp,seg) >= 0 ) { + seg = start - res.p; + if ( Geom::dot(ep,seg) >= 0 ) { + nle = nnle; + } + } + } + + return nle; +} + + +/** + * Simplification on a subpath. + */ + +void Path::DoSimplify(int off, int N, double treshhold) +{ + // non-dichotomic method: grow an interval of points approximated by a curve, until you reach the treshhold, and repeat + // nothing to do with one/zero point(s). + if (N <= 1) { + return; + } + + int curP = 0; + + fitting_tables data; + data.Xk = data.Yk = data.Qk = nullptr; + data.tk = data.lk = nullptr; + data.fk = nullptr; + data.totLen = 0; + data.nbPt = data.maxPt = data.inPt = 0; + + // MoveTo to the first point + Geom::Point const moveToPt = pts[off].p; + MoveTo(moveToPt); + // endToPt stores the last point of each cubic bezier patch (or line segment) that we add + Geom::Point endToPt = moveToPt; + + // curP is a local index and we add the offset (off) in it to get the real + // index. The loop has N - 1 instead of N because there is no point starting + // the fitting process on the last point + while (curP < N - 1) { + // lastP becomes the lastPoint to fit on, basically, we wanna try fitting + // on a sequence of points that start with curP and ends at lastP + // We start with curP being 0 (the first point) and lastP being 1 (the second point) + // M holds the total number of points we are trying to fix + int lastP = curP + 1; + int M = 2; + + // remettre a zero + data.inPt = data.nbPt = 0; + + // a cubic bezier command that will be the patch fitted, which will be appended to the list + // of path commands + PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + // a flag to indicate if there is a forced point in the current sequence that + // we are trying to fit + bool contains_forced = false; + // the fitting code here is called in a binary search fashion, you can say + // that we have fixed the start point for our fitting sequence (curP) and + // need the highest possible endpoint (lastP) (highest in index) such + // that the threshold is respected. + // To do this search, we add "step" number of points to the current sequence + // and fit, if successful, we add "step" more points, if not, we go back to + // the last sequence of points, divide step by 2 and try extending again, this + // process repeats until step becomes 0. + // One more point to mention is how forced points are handled. Once lastP is + // at a forced point, threshold will not be happy with any point after lastP, thus, + // step will slowly reduce to 0, ultimately finishing the patch at the forced point. + int step = 64; + while ( step > 0 ) { + int forced_pt = -1; + int worstP = -1; + // this loop attempts to fit and if the threshold was fine with our fit, we + // add "step" more points to the sequence that we are fitting on + do { + // if the point if forced, we set the flag, basically if there is a forced + // point in the sequence (anywhere), this code will trigger at some point (I think) + if (pts[off + lastP].isMoveTo == polyline_forced) { + contains_forced = true; + } + forced_pt = lastP; // store the forced point (any regular point also gets stored :/) + lastP += step; // move the end marker by + step + M += step; // add "step" to number of points we are trying to fit + // the loop breaks if we either ran out of boundaries or the threshold didn't like + // the fit + } while (lastP < N && ExtendFit(off + curP, M, data, + (contains_forced) ? 0.05 * treshhold : treshhold, // <-- if the last point here is a forced one we + res, worstP) ); // make the threshold really strict so it'll definitely complain about the fit thus + // favoring us to stop and go back by "step" units + // did we go out of boundaries? + if (lastP >= N) { + lastP -= step; // okay, come back by "step" units + M -= step; + } else { // the threshold complained + // le dernier a echoue + lastP -= step; // come back by "step" units + M -= step; + + if ( contains_forced ) { + lastP = forced_pt; // we ensure that we are back at "forced" point. + M = lastP - curP + 1; + } + + // fit stuff again (so we save the results in res); Threshold shouldn't complain + // with this btw + AttemptSimplify(off + curP, M, treshhold, res, worstP); // ca passe forcement + } + step /= 2; // divide step by 2 + } + + // mark lastP as the end point of the sequence we are fitting on + endToPt = pts[off + lastP].p; + // add a patch, for two points a line else a cubic bezier, res has already been calculated + // by AttemptSimplify + if (M <= 2) { + LineTo(endToPt); + } else { + CubicTo(endToPt, res.start, res.end); + } + // next patch starts where this one ended + curP = lastP; + } + + // if the last point that we added is very very close to the first one, it's a loop so close + // it. + if (Geom::LInfty(endToPt - moveToPt) < 0.00001) { + Close(); + } + + g_free(data.Xk); + g_free(data.Yk); + g_free(data.Qk); + g_free(data.tk); + g_free(data.lk); + g_free(data.fk); +} + + +// warning: slow +// idea behind this feature: splotches appear when trying to fit a small number of points: you can +// get a cubic bezier that fits the points very well but doesn't fit the polyline itself +// so we add a bit of the error at the middle of each segment of the polyline +// also we restrict this to <=20 points, to avoid unnecessary computations +#define with_splotch_killer + +// primitive= calc the cubic bezier patche that fits Xk and Yk best +// Qk est deja alloue +// retourne false si probleme (matrice non-inversible) +bool Path::FitCubic(Geom::Point const &start, PathDescrCubicTo &res, + double *Xk, double *Yk, double *Qk, double *tk, int nbPt) +{ + Geom::Point const end = res.p; + + // la matrice tNN + Geom::Affine M(0, 0, 0, 0, 0, 0); + for (int i = 1; i < nbPt - 1; i++) { + M[0] += N13(tk[i]) * N13(tk[i]); + M[1] += N23(tk[i]) * N13(tk[i]); + M[2] += N13(tk[i]) * N23(tk[i]); + M[3] += N23(tk[i]) * N23(tk[i]); + } + + double const det = M.det(); + if (fabs(det) < 0.000001) { + res.start[0]=res.start[1]=0.0; + res.end[0]=res.end[1]=0.0; + return false; + } + + Geom::Affine const iM = M.inverse(); + M = iM; + + // phase 1: abcisses + // calcul des Qk + Xk[0] = start[0]; + Yk[0] = start[1]; + Xk[nbPt - 1] = end[0]; + Yk[nbPt - 1] = end[1]; + + for (int i = 1; i < nbPt - 1; i++) { + Qk[i] = Xk[i] - N03 (tk[i]) * Xk[0] - N33 (tk[i]) * Xk[nbPt - 1]; + } + + // le vecteur Q + Geom::Point Q(0, 0); + for (int i = 1; i < nbPt - 1; i++) { + Q[0] += N13 (tk[i]) * Qk[i]; + Q[1] += N23 (tk[i]) * Qk[i]; + } + + Geom::Point P = Q * M; + Geom::Point cp1; + Geom::Point cp2; + cp1[Geom::X] = P[Geom::X]; + cp2[Geom::X] = P[Geom::Y]; + + // phase 2: les ordonnees + for (int i = 1; i < nbPt - 1; i++) { + Qk[i] = Yk[i] - N03 (tk[i]) * Yk[0] - N33 (tk[i]) * Yk[nbPt - 1]; + } + + // le vecteur Q + Q = Geom::Point(0, 0); + for (int i = 1; i < nbPt - 1; i++) { + Q[0] += N13 (tk[i]) * Qk[i]; + Q[1] += N23 (tk[i]) * Qk[i]; + } + + P = Q * M; + cp1[Geom::Y] = P[Geom::X]; + cp2[Geom::Y] = P[Geom::Y]; + + res.start = 3.0 * (cp1 - start); + res.end = 3.0 * (end - cp2 ); + + return true; +} + + +bool Path::ExtendFit(int off, int N, fitting_tables &data, double treshhold, PathDescrCubicTo &res, int &worstP) +{ + // if N is greater or equal to data.maxPt, reallocate total of 2*N+1 and copy existing data to + // those arrays + if ( N >= data.maxPt ) { + data.maxPt = 2 * N + 1; + data.Xk = (double *) g_realloc(data.Xk, data.maxPt * sizeof(double)); + data.Yk = (double *) g_realloc(data.Yk, data.maxPt * sizeof(double)); + data.Qk = (double *) g_realloc(data.Qk, data.maxPt * sizeof(double)); + data.tk = (double *) g_realloc(data.tk, data.maxPt * sizeof(double)); + data.lk = (double *) g_realloc(data.lk, data.maxPt * sizeof(double)); + data.fk = (char *) g_realloc(data.fk, data.maxPt * sizeof(char)); + } + + // is N greater than data.inPt? data.inPt seems to hold the total number of points stored in X, + // Y, fk of data and thus, we add those that are not there but now should be. + if ( N > data.inPt ) { + for (int i = data.inPt; i < N; i++) { + data.Xk[i] = pts[off + i].p[Geom::X]; + data.Yk[i] = pts[off + i].p[Geom::Y]; + data.fk[i] = ( pts[off + i].isMoveTo == polyline_forced ) ? 0x01 : 0x00; + } + data.lk[0] = 0; + data.tk[0] = 0; + + // calculate the total length of the points that already existed in data + double prevLen = 0; + for (int i = 0; i < data.inPt; i++) { + prevLen += data.lk[i]; + } + data.totLen = prevLen; + + // calculate the lengths data.lk for the new points that we just added + for (int i = ( (data.inPt > 0) ? data.inPt : 1); i < N; i++) { + Geom::Point diff; + diff[Geom::X] = data.Xk[i] - data.Xk[i - 1]; + diff[Geom::Y] = data.Yk[i] - data.Yk[i - 1]; + data.lk[i] = Geom::L2(diff); + data.totLen += data.lk[i]; + data.tk[i] = data.totLen; // set tk[i] to the length so far... + } + + // for the points that existed already, multiply by their previous total length to convert + // the time values into the actual "length so far". + for (int i = 0; i < data.inPt; i++) { + data.tk[i] *= prevLen; + data.tk[i] /= data.totLen; // divide by the new total length to get the newer t value + } + + for (int i = data.inPt; i < N; i++) { // now divide for the newer ones too + data.tk[i] /= data.totLen; + } + data.inPt = N; // update inPt to include the new points too now + } + + // this block is for the situation where you just did a fitting on say 0 to 20 points and now + // you are doing one on 0 to 15 points. N was previously 20 and now it's 15. While your lk + // values are still fine, your tk values are messed up since the tk is 1 at point 20 when + // it should be 1 at point 15 now. So we recalculate tk values. + if ( N < data.nbPt ) { + // We've gone too far; we'll have to recalulate the .tk. + data.totLen = 0; + data.tk[0] = 0; + data.lk[0] = 0; + for (int i = 1; i < N; i++) { + data.totLen += data.lk[i]; + data.tk[i] = data.totLen; + } + + for (int i = 1; i < N; i++) { + data.tk[i] /= data.totLen; + } + } + + data.nbPt = N; + + /* + * There is something that I think is wrong with this implementation. Say we initially fit on + * point 0 to 10, it works well, so we add 10 more points and fit on 0-20 points. Out tk values + * are correct so far. That fit is problematic so maybe we fit on 0-15 but now, N < data.nbPt + * block will run and recalculate tk values. So we will have correct tk values from 0-15 but + * the older tk values in 15-20. Say after this we fit on 0-18 points. Well we ran into a + * problem N > data.nbPt so no recalculation happens. data.inPt is already 20 so nothing + * happens in that block either. We have invalid tk values and FitCubic will be called with + * these invalid values. I've drawn paths and seen this situation where FitCubic was called + * with wrong tk values. Values that would go 0, 0.25, 0.5, 0.75, 1, 0.80, 0.90. Maybe the + * consequences are not that terrible so we didn't notice this in results? + * TODO: Do we fix this or investigate more? + */ + + if ( data.nbPt <= 0 ) { + return false; + } + + res.p[0] = data.Xk[data.nbPt - 1]; + res.p[1] = data.Yk[data.nbPt - 1]; + res.start[0] = res.start[1] = 0; + res.end[0] = res.end[1] = 0; + worstP = 1; + if ( N <= 2 ) { + return true; + } + // this is the same popular block that's found in the other AttemptSimplify so please go there + // to see what it does and how + if ( data.totLen < 0.0001 ) { + double worstD = 0; + Geom::Point start; + worstP = -1; + start[0] = data.Xk[0]; + start[1] = data.Yk[0]; + for (int i = 1; i < N; i++) { + Geom::Point nPt; + bool isForced = data.fk[i]; + nPt[0] = data.Xk[i]; + nPt[1] = data.Yk[i]; + + double nle = DistanceToCubic(start, res, nPt); + if ( isForced ) { + // forced points are favored for splitting the recursion; we do this by increasing their distance + if ( worstP < 0 || 2*nle > worstD ) { + worstP = i; + worstD = 2*nle; + } + } else { + if ( worstP < 0 || nle > worstD ) { + worstP = i; + worstD = nle; + } + } + } + return true; // it's weird that this is the only block of this kind that returns true instead of false. Livarot + // can you please be more consistent? -_- + } + + return AttemptSimplify(data, treshhold, res, worstP); +} + + +// fit a polyline to a bezier patch, return true is treshhold not exceeded (ie: you can continue) +// version that uses tables from the previous iteration, to minimize amount of work done +bool Path::AttemptSimplify (fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP) +{ + Geom::Point start,end; + // pour une coordonnee + Geom::Point cp1, cp2; + + worstP = 1; + if (pts.size() == 2) { + return true; + } + + start[0] = data.Xk[0]; + start[1] = data.Yk[0]; + cp1[0] = data.Xk[1]; + cp1[1] = data.Yk[1]; + end[0] = data.Xk[data.nbPt - 1]; + end[1] = data.Yk[data.nbPt - 1]; + cp2 = cp1; + + if (pts.size() == 3) { + // start -> cp1 -> end + res.start = cp1 - start; + res.end = end - cp1; + worstP = 1; + return true; + } + + if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) ) { + cp1 = start + res.start / 3; + cp2 = end - res.end / 3; + } else { + // aie, non-inversible + double worstD = 0; + worstP = -1; + for (int i = 1; i < data.nbPt; i++) { + Geom::Point nPt; + nPt[Geom::X] = data.Xk[i]; + nPt[Geom::Y] = data.Yk[i]; + double nle = DistanceToCubic(start, res, nPt); + if ( data.fk[i] ) { + // forced points are favored for splitting the recursion; we do this by increasing their distance + if ( worstP < 0 || 2 * nle > worstD ) { + worstP = i; + worstD = 2 * nle; + } + } else { + if ( worstP < 0 || nle > worstD ) { + worstP = i; + worstD = nle; + } + } + } + return false; + } + + // calcul du delta= pondere par les longueurs des segments + double delta = 0; + { + double worstD = 0; + worstP = -1; + Geom::Point prevAppP; + Geom::Point prevP; + double prevDist; + prevP[Geom::X] = data.Xk[0]; + prevP[Geom::Y] = data.Yk[0]; + prevAppP = prevP; // le premier seulement + prevDist = 0; +#ifdef with_splotch_killer + if ( data.nbPt <= 20 ) { + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + Geom::Point midAppP; + Geom::Point midP; + double midDist; + + curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + + N23(data.tk[i]) * cp2[Geom::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + + N23(data.tk[i]) * cp2[Geom::Y] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[Geom::X] = data.Xk[i]; + curP[Geom::Y] = data.Yk[i]; + double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]); + + midAppP[Geom::X] = N13(mtk) * cp1[Geom::X] + + N23(mtk) * cp2[Geom::X] + + N03(mtk) * data.Xk[0] + + N33(mtk) * data.Xk[data.nbPt - 1]; + + midAppP[Geom::Y] = N13(mtk) * cp1[Geom::Y] + + N23(mtk) * cp2[Geom::Y] + + N03(mtk) * data.Yk[0] + + N33(mtk) * data.Yk[data.nbPt - 1]; + + midP = 0.5 * (curP + prevP); + + Geom::Point diff = curAppP - curP; + curDist = dot(diff, diff); + diff = midAppP - midP; + midDist = dot(diff, diff); + + delta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i]; + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( data.fk[i] && 2 * curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } + delta /= data.totLen; + + } else { +#endif + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + + curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + + N23(data.tk[i]) * cp2[Geom::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + + N23(data.tk[i]) * cp2[Geom::Y] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[Geom::X] = data.Xk[i]; + curP[Geom::Y] = data.Yk[i]; + + Geom::Point diff = curAppP-curP; + curDist = dot(diff, diff); + delta += curDist; + + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( data.fk[i] && 2 * curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } +#ifdef with_splotch_killer + } +#endif + } + + if (delta < treshhold * treshhold) { + // premier jet + + // Refine a little. + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point pt(data.Xk[i], data.Yk[i]); + data.tk[i] = RaffineTk(pt, start, cp1, cp2, end, data.tk[i]); + if (data.tk[i] < data.tk[i - 1]) { + // Force tk to be monotonic non-decreasing. + data.tk[i] = data.tk[i - 1]; + } + } + + if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) == false) { + // ca devrait jamais arriver, mais bon + res.start = 3.0 * (cp1 - start); + res.end = 3.0 * (end - cp2 ); + return true; + } + + double ndelta = 0; + { + double worstD = 0; + worstP = -1; + Geom::Point prevAppP; + Geom::Point prevP(data.Xk[0], data.Yk[0]); + double prevDist = 0; + prevAppP = prevP; // le premier seulement +#ifdef with_splotch_killer + if ( data.nbPt <= 20 ) { + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + Geom::Point midAppP; + Geom::Point midP; + double midDist; + + curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + + N23(data.tk[i]) * cp2[Geom::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + + N23(data.tk[i]) * cp2[Geom::Y] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[Geom::X] = data.Xk[i]; + curP[Geom::Y] = data.Yk[i]; + double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]); + + midAppP[Geom::X] = N13(mtk) * cp1[Geom::X] + + N23(mtk) * cp2[Geom::X] + + N03(mtk) * data.Xk[0] + + N33(mtk) * data.Xk[data.nbPt - 1]; + + midAppP[Geom::Y] = N13(mtk) * cp1[Geom::Y] + + N23(mtk) * cp2[Geom::Y] + + N03(mtk) * data.Yk[0] + + N33(mtk) * data.Yk[data.nbPt - 1]; + + midP = 0.5 * (curP + prevP); + + Geom::Point diff = curAppP - curP; + curDist = dot(diff, diff); + + diff = midAppP - midP; + midDist = dot(diff, diff); + + ndelta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i]; + + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( data.fk[i] && 2 * curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } + ndelta /= data.totLen; + } else { +#endif + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + + curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + + N23(data.tk[i]) * cp2[Geom::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + + N23(data.tk[i]) * cp2[1] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[Geom::X] = data.Xk[i]; + curP[Geom::Y] = data.Yk[i]; + + Geom::Point diff = curAppP - curP; + curDist = dot(diff, diff); + + ndelta += curDist; + + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( data.fk[i] && 2 * curDist > worstD ) { + worstD = 2 * curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } +#ifdef with_splotch_killer + } +#endif + } + + if (ndelta < delta + 0.00001) { + return true; + } else { + // nothing better to do + res.start = 3.0 * (cp1 - start); + res.end = 3.0 * (end - cp2 ); + } + + return true; + } + + return false; +} + + +bool Path::AttemptSimplify(int off, int N, double treshhold, PathDescrCubicTo &res,int &worstP) +{ + Geom::Point start; + Geom::Point end; + + // pour une coordonnee + double *Xk; // la coordonnee traitee (x puis y) + double *Yk; // la coordonnee traitee (x puis y) + double *lk; // les longueurs de chaque segment + double *tk; // les tk + double *Qk; // les Qk + char *fk; // si point force + + Geom::Point cp1; // first control point of the cubic patch + Geom::Point cp2; // second control point of the cubic patch + + // If only two points, return immediately, but why say that point 1 has worst error though? + if (N == 2) { + worstP = 1; + return true; + } + + start = pts[off].p; // point at index "off" is start + cp1 = pts[off + 1].p; // for now take the first control point to be the point after "off" + end = pts[off + N - 1].p; // last point would be end of the patch of course + + // we will return the control handles in "res" right? so set the end point but set the handles + // to zero for now + res.p = end; + res.start[0] = res.start[1] = 0; + res.end[0] = res.end[1] = 0; + + // if there are 3 points only, we fit a cubic patch that starts at the first point, ends at the + // third point and has both control points on the 2nd point + if (N == 3) { + // start -> cp1 -> end + res.start = cp1 - start; + res.end = end - cp1; + worstP = 1; + return true; + } + + // Totally inefficient, allocates & deallocates all the time. + // allocate arrows for fitting information + tk = (double *) g_malloc(N * sizeof(double)); + Qk = (double *) g_malloc(N * sizeof(double)); + Xk = (double *) g_malloc(N * sizeof(double)); + Yk = (double *) g_malloc(N * sizeof(double)); + lk = (double *) g_malloc(N * sizeof(double)); + fk = (char *) g_malloc(N * sizeof(char)); + + // chord length method + tk[0] = 0.0; + lk[0] = 0.0; + { + // Not setting Xk[0], Yk[0] might look like a bug, but FitCubic sets it later before + // it performs the fit + Geom::Point prevP = start; // store the first point + for (int i = 1; i < N; i++) { // start the loop on the second point + Xk[i] = pts[off + i].p[Geom::X]; // store the x coordinate + Yk[i] = pts[off + i].p[Geom::Y]; // store the y coordinate + + // mark the point as forced if it's forced + if ( pts[off + i].isMoveTo == polyline_forced ) { + fk[i] = 0x01; + } else { + fk[i] = 0; + } + + // calculate a vector from previous point to this point and store its length in lk + Geom::Point diff(Xk[i] - prevP[Geom::X], Yk[i] - prevP[1]); + prevP[0] = Xk[i]; // set prev to current point for next iteration + prevP[1] = Yk[i]; // set prev to current point for next iteration + lk[i] = Geom::L2(diff); + tk[i] = tk[i - 1] + lk[i]; // tk should have the length till this point, later we divide whole thing by total length to calculate time + } + } + + // if total length is below 0.00001 (very unrealistic scenario) + if (tk[N - 1] < 0.00001) { + // longueur nulle + res.start[0] = res.start[1] = 0; // set start handle to zero + res.end[0] = res.end[1] = 0; // set end handle to zero + double worstD = 0; // worst distance b/w the curve and the polyline (to fit) + worstP = -1; // point at which worst difference came up + for (int i = 1; i < N; i++) { // start at second point till the last one + Geom::Point nPt; + bool isForced = fk[i]; + nPt[0] = Xk[i]; // get the point + nPt[1] = Yk[i]; + + double nle = DistanceToCubic(start, res, nPt); // distance from point. see the documentation on that function + if ( isForced ) { + // forced points are favored for splitting the recursion; we do this by increasing their distance + if ( worstP < 0 || 2 * nle > worstD ) { // exaggerating distance for the forced point? + worstP = i; + worstD = 2 * nle; + } + } else { + if ( worstP < 0 || nle > worstD ) { // if worstP not set or the distance for this point is greater than previous worse + worstP = i; // make this one the worse + worstD = nle; + } + } + } + + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + + return false; // not sure why we return false? because the length of points to fit is zero? + // anyways, rare case, so fine to return false and a cubic bezier that starts + // at first and ends at last point with control points being same as + // endpoins. + } + + // divide by total length to make "time" values. See documentation of fitting_table structure + double totLen = tk[N - 1]; + for (int i = 1; i < N - 1; i++) { + tk[i] /= totLen; + } + + res.p = end; + if ( FitCubic(start, res, Xk, Yk, Qk, tk, N) ) { // fit a cubic, if goes well, set cp1 and cp2 + cp1 = start + res.start / 3; // this factor of three comes from the fact that this is how livarot stores them. See docs in path-description.h + cp2 = end + res.end / 3; + } else { + // aie, non-inversible + // okay we couldn't fit one, don't know when this would happen but probably due to matrix + // being non-inversible (no idea when that would happen either) + + // same code from above, calculates error (kinda, see comments on DistanceToCubic) and returns false + res.start[0] = res.start[1] = 0; + res.end[0] = res.end[1] = 0; + double worstD = 0; + worstP = -1; + for (int i = 1; i < N; i++) { + Geom::Point nPt(Xk[i], Yk[i]); + bool isForced = fk[i]; + double nle = DistanceToCubic(start, res, nPt); + if ( isForced ) { + // forced points are favored for splitting the recursion; we do this by increasing their distance + if ( worstP < 0 || 2 * nle > worstD ) { + worstP = i; + worstD = 2 * nle; + } + } else { + if ( worstP < 0 || nle > worstD ) { + worstP = i; + worstD = nle; + } + } + } + + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + return false; + } + + // calcul du delta= pondere par les longueurs des segments + // if we are here, fitting went well, let's calculate error between the cubic bezier that we + // fit and the actual polyline + double delta = 0; + { + double worstD = 0; + worstP = -1; + Geom::Point prevAppP; + Geom::Point prevP; + double prevDist; + prevP[0] = Xk[0]; + prevP[1] = Yk[0]; + prevAppP = prevP; // le premier seulement + prevDist = 0; +#ifdef with_splotch_killer // <-- ignore the splotch killer if you're new and go to the part where this (N <= 20) if's else is + // so the thing is, if the number of points you're fitting on are few, you can often have a + // fit where the error on your polyline points are zero but it's a terrible fit, for + // example imagine three point o------o-------o and an S fitting on these. Error is zero, + // but it's a terrible fit. So to fix this problem, we calculate error at mid points of the + // line segments too, to be a better judge, and if it's terrible, we just reject this fit + // and the parent algorithm will do something about it (fit a smaller one or just do a line + // segment) + if ( N <= 20 ) { + for (int i = 1; i < N - 1; i++) // for every point that's not an endpoint + { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + Geom::Point midAppP; + Geom::Point midP; + double midDist; + + curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; // point on the curve + curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1]; + curP[0] = Xk[i]; // polyline point + curP[1] = Yk[i]; + midAppP[0] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[0] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[0] + N03 (0.5*(tk[i]+tk[i-1])) * Xk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Xk[N - 1]; // midpoint on the curve + midAppP[1] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[1] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[1] + N03 (0.5*(tk[i]+tk[i-1])) * Yk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Yk[N - 1]; + midP=0.5*(curP+prevP); // midpoint on the polyline + + Geom::Point diff; + diff = curAppP-curP; + curDist = dot(diff,diff); // squared distance between point on cubic curve and the polyline point + + diff = midAppP-midP; + midDist = dot(diff,diff); // squared distance between midpoint on polyline and the equivalent on cubic curve + + delta+=0.3333*(curDist+prevDist+midDist)/**lk[i]*/; // The multiplication by lk[i] here kind of creates a weightage mechanism + // we divide delta by total length afterwards, so line segments with more share in + // the length get weighted more. Why do we care about prevDist though? Anyways, + // it's a way of measuring error, so probably fine + + if ( curDist > worstD ) { // worst error management + worstD = curDist; + worstP = i; + } else if ( fk[i] && 2*curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; // <-- useless + prevDist = curDist; + } + delta/=totLen; + } else { +#endif + for (int i = 1; i < N - 1; i++) // for each point in the polyline that's not an end point + { + Geom::Point curAppP; // the current point on the bezier patch + Geom::Point curP; // the polyline point + double curDist; + + // https://en.wikipedia.org/wiki/B%C3%A9zier_curve + curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; // <-- cubic bezier function + curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1]; + curP[0] = Xk[i]; + curP[1] = Yk[i]; + + Geom::Point diff; + diff = curAppP-curP; // difference between the two + curDist = dot(diff,diff); // square of the difference + delta += curDist; // add to total error + if ( curDist > worstD ) { // management of worst error (max finder basically) + worstD = curDist; + worstP = i; + } else if ( fk[i] && 2*curDist > worstD ) { // it wasn't the worse, but if it's forced point, judge more harshly + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; // <-- we are not using these + prevDist = curDist; // <-- we are not using these + } +#ifdef with_splotch_killer + } +#endif + } + + // are we fine with the error? Compare it to threshold squared + if (delta < treshhold * treshhold) + { + // premier jet + res.start = 3.0 * (cp1 - start); // calculate handles + res.end = -3.0 * (cp2 - end); + res.p = end; + + // Refine a little. + for (int i = 1; i < N - 1; i++) // do Newton Raphson iterations + { + Geom::Point + pt; + pt[0] = Xk[i]; + pt[1] = Yk[i]; + tk[i] = RaffineTk (pt, start, cp1, cp2, end, tk[i]); + if (tk[i] < tk[i - 1]) + { + // Force tk to be monotonic non-decreasing. + tk[i] = tk[i - 1]; + } + } + + if ( FitCubic(start,res,Xk,Yk,Qk,tk,N) ) { // fit again and this should work + } else { // just in case it doesn't + // ca devrait jamais arriver, mais bon + res.start = 3.0 * (cp1 - start); + res.end = -3.0 * (cp2 - end); + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + return true; + } + double ndelta = 0; // the same error computing stuff with and without splotch killer as before + { + double worstD = 0; + worstP = -1; + Geom::Point prevAppP; + Geom::Point prevP; + double prevDist; + prevP[0] = Xk[0]; + prevP[1] = Yk[0]; + prevAppP = prevP; // le premier seulement + prevDist = 0; +#ifdef with_splotch_killer + if ( N <= 20 ) { + for (int i = 1; i < N - 1; i++) + { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + Geom::Point midAppP; + Geom::Point midP; + double midDist; + + curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; + curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1]; + curP[0] = Xk[i]; + curP[1] = Yk[i]; + midAppP[0] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[0] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[0] + N03 (0.5*(tk[i]+tk[i-1])) * Xk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Xk[N - 1]; + midAppP[1] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[1] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[1] + N03 (0.5*(tk[i]+tk[i-1])) * Yk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Yk[N - 1]; + midP = 0.5*(curP+prevP); + + Geom::Point diff; + diff = curAppP-curP; + curDist = dot(diff,diff); + diff = midAppP-midP; + midDist = dot(diff,diff); + + ndelta+=0.3333*(curDist+prevDist+midDist)/**lk[i]*/; + + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( fk[i] && 2*curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } + ndelta /= totLen; + } else { +#endif + for (int i = 1; i < N - 1; i++) + { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + + curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; + curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1]; + curP[0]=Xk[i]; + curP[1]=Yk[i]; + + Geom::Point diff; + diff=curAppP-curP; + curDist=dot(diff,diff); + ndelta+=curDist; + + if ( curDist > worstD ) { + worstD=curDist; + worstP=i; + } else if ( fk[i] && 2*curDist > worstD ) { + worstD=2*curDist; + worstP=i; + } + prevP=curP; + prevAppP=curAppP; + prevDist=curDist; + } +#ifdef with_splotch_killer + } +#endif + } + + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + + if (ndelta < delta + 0.00001) // is the new one after newton raphson better? they are stored in "res" + { + return true; // okay great return those handles. + } else { + // nothing better to do + res.start = 3.0 * (cp1 - start); // new ones aren't better? use the old ones stored in cp1 and cp2 + res.end = -3.0 * (cp2 - end); + } + return true; + } else { // threshold got disrespected, return false + // nothing better to do + } + + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + return false; +} + +double Path::RaffineTk (Geom::Point pt, Geom::Point p0, Geom::Point p1, Geom::Point p2, Geom::Point p3, double it) +{ + // Refinement of the tk values. + // Just one iteration of Newtow Raphson, given that we're approaching the curve anyway. + // [fr: vu que de toute facon la courbe est approchC)e] + double const Ax = pt[Geom::X] - + p0[Geom::X] * N03(it) - + p1[Geom::X] * N13(it) - + p2[Geom::X] * N23(it) - + p3[Geom::X] * N33(it); + + double const Bx = (p1[Geom::X] - p0[Geom::X]) * N02(it) + + (p2[Geom::X] - p1[Geom::X]) * N12(it) + + (p3[Geom::X] - p2[Geom::X]) * N22(it); + + double const Cx = (p0[Geom::X] - 2 * p1[Geom::X] + p2[Geom::X]) * N01(it) + + (p3[Geom::X] - 2 * p2[Geom::X] + p1[Geom::X]) * N11(it); + + double const Ay = pt[Geom::Y] - + p0[Geom::Y] * N03(it) - + p1[Geom::Y] * N13(it) - + p2[Geom::Y] * N23(it) - + p3[Geom::Y] * N33(it); + + double const By = (p1[Geom::Y] - p0[Geom::Y]) * N02(it) + + (p2[Geom::Y] - p1[Geom::Y]) * N12(it) + + (p3[Geom::Y] - p2[Geom::Y]) * N22(it); + + double const Cy = (p0[Geom::Y] - 2 * p1[Geom::Y] + p2[Geom::Y]) * N01(it) + + (p3[Geom::Y] - 2 * p2[Geom::Y] + p1[Geom::Y]) * N11(it); + + double const dF = -6 * (Ax * Bx + Ay * By); + double const ddF = 18 * (Bx * Bx + By * By) - 12 * (Ax * Cx + Ay * Cy); + if (fabs (ddF) > 0.0000001) { + return it - dF / ddF; + } + + return it; +} + +// Variation on the fitting theme: try to merge path commands into cubic bezier patches. +// The goal is to reduce the number of path commands, especially when operations on path produce +// lots of small path elements; ideally you could get rid of very small segments at reduced visual cost. +void Path::Coalesce(double tresh) +{ + if ( descr_flags & descr_adding_bezier ) { + CancelBezier(); + } + + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } + + if (descr_cmd.size() <= 2) { + return; + } + + SetBackData(false); + Path* tempDest = new Path(); + tempDest->SetBackData(false); + + ConvertEvenLines(0.25*tresh); + + int lastP = 0; + int lastAP = -1; + // As the elements are stored in a separate array, it's no longer worth optimizing + // the rewriting in the same array. + // [[comme les elements sont stockes dans un tableau a part, plus la peine d'optimiser + // la réécriture dans la meme tableau]] + + int lastA = descr_cmd[0]->associated; + int prevA = lastA; + Geom::Point firstP; + + /* FIXME: the use of this variable probably causes a leak or two. + ** It's a hack anyway, and probably only needs to be a type rather than + ** a full PathDescr. + */ + std::unique_ptr<PathDescr> lastAddition(new PathDescrMoveTo(Geom::Point(0, 0))); + bool containsForced = false; + PathDescrCubicTo pending_cubic(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + + for (int curP = 0; curP < int(descr_cmd.size()); curP++) { + int typ = descr_cmd[curP]->getType(); + int nextA = lastA; + + if (typ == descr_moveto) { + + if (lastAddition->flags != descr_moveto) { + FlushPendingAddition(tempDest,lastAddition.get(),pending_cubic,lastAP); + } + lastAddition.reset(descr_cmd[curP]->clone()); + lastAP = curP; + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + // Added automatically (too bad about multiple moveto's). + // [fr: (tant pis pour les moveto multiples)] + containsForced = false; + + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]); + firstP = nData->p; + lastA = descr_cmd[curP]->associated; + prevA = lastA; + lastP = curP; + + } else if (typ == descr_close) { + nextA = descr_cmd[curP]->associated; + if (lastAddition->flags != descr_moveto) { + + PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + int worstP = -1; + if (AttemptSimplify(lastA, nextA - lastA + 1, (containsForced) ? 0.05 * tresh : tresh, res, worstP)) { + lastAddition.reset(new PathDescrCubicTo(Geom::Point(0, 0), + Geom::Point(0, 0), + Geom::Point(0, 0))); + pending_cubic = res; + lastAP = -1; + } + + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + FlushPendingAddition(tempDest, descr_cmd[curP], pending_cubic, curP); + + } else { + FlushPendingAddition(tempDest,descr_cmd[curP],pending_cubic,curP); + } + + containsForced = false; + lastAddition.reset(new PathDescrMoveTo(Geom::Point(0, 0))); + prevA = lastA = nextA; + lastP = curP; + lastAP = curP; + + } else if (typ == descr_forced) { + + nextA = descr_cmd[curP]->associated; + if (lastAddition->flags != descr_moveto) { + + PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + int worstP = -1; + if (AttemptSimplify(lastA, nextA - lastA + 1, 0.05 * tresh, res, worstP)) { + // plus sensible parce que point force + // ca passe + /* (Possible translation: More sensitive because contains a forced point.) */ + containsForced = true; + } else { + // Force the addition. + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + lastAddition.reset(new PathDescrMoveTo(Geom::Point(0, 0))); + prevA = lastA = nextA; + lastP = curP; + lastAP = curP; + containsForced = false; + } + } + + } else if (typ == descr_lineto || typ == descr_cubicto || typ == descr_arcto) { + + nextA = descr_cmd[curP]->associated; + if (lastAddition->flags != descr_moveto) { + + PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + int worstP = -1; + if (AttemptSimplify(lastA, nextA - lastA + 1, tresh, res, worstP)) { + lastAddition.reset(new PathDescrCubicTo(Geom::Point(0, 0), + Geom::Point(0, 0), + Geom::Point(0, 0))); + pending_cubic = res; + lastAddition->associated = lastA; + lastP = curP; + lastAP = -1; + } else { + lastA = descr_cmd[lastP]->associated; // pourrait etre surecrit par la ligne suivante + /* (possible translation: Could be overwritten by the next line.) */ + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + lastAddition.reset(descr_cmd[curP]->clone()); + if ( typ == descr_cubicto ) { + pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP])); + } + lastAP = curP; + containsForced = false; + } + + } else { + lastA = prevA /*descr_cmd[curP-1]->associated */ ; + lastAddition.reset(descr_cmd[curP]->clone()); + if ( typ == descr_cubicto ) { + pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP])); + } + lastAP = curP; + containsForced = false; + } + prevA = nextA; + + } else if (typ == descr_bezierto) { + + if (lastAddition->flags != descr_moveto) { + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + lastAddition.reset(new PathDescrMoveTo(Geom::Point(0, 0))); + } + lastAP = -1; + lastA = descr_cmd[curP]->associated; + lastP = curP; + PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo*>(descr_cmd[curP]); + for (int i = 1; i <= nBData->nb; i++) { + FlushPendingAddition(tempDest, descr_cmd[curP + i], pending_cubic, curP + i); + } + curP += nBData->nb; + prevA = nextA; + + } else if (typ == descr_interm_bezier) { + continue; + } else { + continue; + } + } + + if (lastAddition->flags != descr_moveto) { + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + } + + Copy(tempDest); + delete tempDest; +} + + +void Path::FlushPendingAddition(Path *dest, PathDescr *lastAddition, + PathDescrCubicTo &lastCubic, int lastAP) +{ + switch (lastAddition->getType()) { + + case descr_moveto: + if ( lastAP >= 0 ) { + PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[lastAP]); + dest->MoveTo(nData->p); + } + break; + + case descr_close: + dest->Close(); + break; + + case descr_cubicto: + dest->CubicTo(lastCubic.p, lastCubic.start, lastCubic.end); + break; + + case descr_lineto: + if ( lastAP >= 0 ) { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[lastAP]); + dest->LineTo(nData->p); + } + break; + + case descr_arcto: + if ( lastAP >= 0 ) { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[lastAP]); + dest->ArcTo(nData->p, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise); + } + break; + + case descr_bezierto: + if ( lastAP >= 0 ) { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[lastAP]); + dest->BezierTo(nData->p); + } + break; + + case descr_interm_bezier: + if ( lastAP >= 0 ) { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[lastAP]); + dest->IntermBezierTo(nData->p); + } + break; + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/PathStroke.cpp b/src/livarot/PathStroke.cpp new file mode 100644 index 0000000..e70ce36 --- /dev/null +++ b/src/livarot/PathStroke.cpp @@ -0,0 +1,763 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "Path.h" +#include "Shape.h" +#include <2geom/transforms.h> + +/* + * stroking polylines into a Shape instance + * grunt work. + * if the goal is to raster the stroke, polyline stroke->polygon->uncrossed polygon->raster is grossly + * inefficient (but reuse the intersector, so that's what a lazy programmer like me does). the correct way would be + * to set up a supersampled buffer, raster each polyline stroke's part (one part per segment in the polyline, plus + * each join) because these are all convex polygons, then transform in alpha values + */ + +// until i find something better +static Geom::Point StrokeNormalize(const Geom::Point value) { + double length = L2(value); + if ( length < 0.0000001 ) { + return Geom::Point(0, 0); + } else { + return value/length; + } +} + +// faster version if length is known +static Geom::Point StrokeNormalize(const Geom::Point value, double length) { + if ( length < 0.0000001 ) { + return Geom::Point(0, 0); + } else { + return value/length; + } +} + +void Path::Stroke(Shape *dest, bool doClose, double width, JoinType join, + ButtType butt, double miter, bool justAdd) +{ + if (dest == nullptr) { + return; + } + + if (justAdd == false) { + dest->Reset(3 * pts.size(), 3 * pts.size()); + } + + dest->MakeBackData(false); + + int lastM = 0; + while (lastM < int(pts.size())) { + + int lastP = lastM + 1; + while (lastP < int(pts.size()) // select one subpath + && (pts[lastP].isMoveTo == polyline_lineto + || pts[lastP].isMoveTo == polyline_forced)) + { + lastP++; + } + + if ( lastP > lastM+1 ) { + Geom::Point sbStart = pts[lastM].p; + Geom::Point sbEnd = pts[lastP - 1].p; + // if ( pts[lastP - 1].closed ) { // this is correct, but this bugs text rendering (doesn't close text stroke)... + if ( Geom::LInfty(sbEnd-sbStart) < 0.00001 ) { // why close lines that shouldn't be closed? + // ah I see, because close is defined here for + // a whole path and should be defined per subpath. + // debut==fin => ferme (on devrait garder un element pour les close(), mais tant pis) + DoStroke(lastM, lastP - lastM, dest, true, width, join, butt, miter, true); + } else { + DoStroke(lastM, lastP - lastM, dest, doClose, width, join, butt, miter, true); + } + } else if (butt == butt_round) { // special case: zero length round butt is a circle + int last[2] = { -1, -1 }; + Geom::Point dir; + dir[0] = 1; + dir[1] = 0; + Geom::Point pos = pts[lastM].p; + DoButt(dest, width, butt, pos, dir, last[RIGHT], last[LEFT]); + int end[2]; + dir = -dir; + DoButt(dest, width, butt, pos, dir, end[LEFT], end[RIGHT]); + dest->AddEdge (end[LEFT], last[LEFT]); + dest->AddEdge (last[RIGHT], end[RIGHT]); + } + lastM = lastP; + } +} + +void Path::DoStroke(int off, int N, Shape *dest, bool doClose, double width, JoinType join, + ButtType butt, double miter, bool /*justAdd*/) +{ + if (N <= 1) { + return; + } + + Geom::Point prevP, nextP; + int prevI, nextI; + int upTo; + + int curI = 0; + Geom::Point curP = pts[off].p; + + if (doClose) { + + prevI = N - 1; + while (prevI > 0) { + prevP = pts[off + prevI].p; + Geom::Point diff = curP - prevP; + double dist = dot(diff, diff); + if (dist > 0.001) { + break; + } + prevI--; + } + if (prevI <= 0) { + return; + } + upTo = prevI; + + } else { + + prevP = curP; + prevI = curI; + upTo = N - 1; + } + + { + nextI = 1; + while (nextI <= upTo) { + nextP = pts[off + nextI].p; + Geom::Point diff = curP - nextP; + double dist = dot(diff, diff); + if (dist > 0.0) { // more tolerance for the first distance, to give the cap the right direction + break; + } + nextI++; + } + if (nextI > upTo) { + if (butt == butt_round) { // special case: zero length round butt is a circle + int last[2] = { -1, -1 }; + Geom::Point dir; + dir[0] = 1; + dir[1] = 0; + DoButt(dest, width, butt, curP, dir, last[RIGHT], last[LEFT]); + int end[2]; + dir = -dir; + DoButt(dest, width, butt, curP, dir, end[LEFT], end[RIGHT]); + dest->AddEdge (end[LEFT], last[LEFT]); + dest->AddEdge (last[RIGHT], end[RIGHT]); + } + return; + } + } + + int start[2] = { -1, -1 }; + int last[2] = { -1, -1 }; + Geom::Point prevD = curP - prevP; + Geom::Point nextD = nextP - curP; + double prevLe = Geom::L2(prevD); + double nextLe = Geom::L2(nextD); + prevD = StrokeNormalize(prevD, prevLe); + nextD = StrokeNormalize(nextD, nextLe); + + if (doClose) { + DoJoin(dest, width, join, curP, prevD, nextD, miter, prevLe, nextLe, start, last); + } else { + nextD = -nextD; + DoButt(dest, width, butt, curP, nextD, last[RIGHT], last[LEFT]); + nextD = -nextD; + } + + do { + prevP = curP; + prevI = curI; + curP = nextP; + curI = nextI; + prevD = nextD; + prevLe = nextLe; + nextI++; + while (nextI <= upTo) { + nextP = pts[off + nextI].p; + Geom::Point diff = curP - nextP; + double dist = dot(diff, diff); + if (dist > 0.001 || (nextI == upTo && dist > 0.0)) { // more tolerance for the last distance too, for the right cap direction + break; + } + nextI++; + } + if (nextI > upTo) { + break; + } + + nextD = nextP - curP; + nextLe = Geom::L2(nextD); + nextD = StrokeNormalize(nextD, nextLe); + int nSt[2] = { -1, -1 }; + int nEn[2] = { -1, -1 }; + DoJoin(dest, width, join, curP, prevD, nextD, miter, prevLe, nextLe, nSt, nEn); + dest->AddEdge(nSt[LEFT], last[LEFT]); + last[LEFT] = nEn[LEFT]; + dest->AddEdge(last[RIGHT], nSt[RIGHT]); + last[RIGHT] = nEn[RIGHT]; + } while (nextI <= upTo); + + if (doClose) { + /* prevP=curP; + prevI=curI; + curP=nextP; + curI=nextI; + prevD=nextD;*/ + nextP = pts[off].p; + + nextD = nextP - curP; + nextLe = Geom::L2(nextD); + nextD = StrokeNormalize(nextD, nextLe); + int nSt[2] = { -1, -1 }; + int nEn[2] = { -1, -1 }; + DoJoin(dest, width, join, curP, prevD, nextD, miter, prevLe, nextLe, nSt, nEn); + dest->AddEdge (nSt[LEFT], last[LEFT]); + last[LEFT] = nEn[LEFT]; + dest->AddEdge (last[RIGHT], nSt[RIGHT]); + last[RIGHT] = nEn[RIGHT]; + + dest->AddEdge (start[LEFT], last[LEFT]); + dest->AddEdge (last[RIGHT], start[RIGHT]); + + } else { + + int end[2]; + DoButt (dest, width, butt, curP, prevD, end[LEFT], end[RIGHT]); + dest->AddEdge (end[LEFT], last[LEFT]); + dest->AddEdge (last[RIGHT], end[RIGHT]); + } +} + + +void Path::DoButt(Shape *dest, double width, ButtType butt, Geom::Point pos, Geom::Point dir, + int &leftNo, int &rightNo) +{ + Geom::Point nor; + nor = dir.ccw(); + + if (butt == butt_square) + { + Geom::Point x; + x = pos + width * dir + width * nor; + int bleftNo = dest->AddPoint (x); + x = pos + width * dir - width * nor; + int brightNo = dest->AddPoint (x); + x = pos + width * nor; + leftNo = dest->AddPoint (x); + x = pos - width * nor; + rightNo = dest->AddPoint (x); + dest->AddEdge (rightNo, brightNo); + dest->AddEdge (brightNo, bleftNo); + dest->AddEdge (bleftNo, leftNo); + } + else if (butt == butt_pointy) + { + leftNo = dest->AddPoint (pos + width * nor); + rightNo = dest->AddPoint (pos - width * nor); + int mid = dest->AddPoint (pos + width * dir); + dest->AddEdge (rightNo, mid); + dest->AddEdge (mid, leftNo); + } + else if (butt == butt_round) + { + const Geom::Point sx = pos + width * nor; + const Geom::Point ex = pos - width * nor; + leftNo = dest->AddPoint (sx); + rightNo = dest->AddPoint (ex); + + RecRound (dest, rightNo, leftNo, ex, sx, -nor, nor, pos, width); + } + else + { + leftNo = dest->AddPoint (pos + width * nor); + rightNo = dest->AddPoint (pos - width * nor); + dest->AddEdge (rightNo, leftNo); + } +} + + +void Path::DoJoin (Shape *dest, double width, JoinType join, Geom::Point pos, Geom::Point prev, + Geom::Point next, double miter, double /*prevL*/, double /*nextL*/, + int *stNo, int *enNo) +{ + Geom::Point pnor = prev.ccw(); + Geom::Point nnor = next.ccw(); + double angSi = cross(prev, next); + + /* FIXED: this special case caused bug 1028953 */ + if (angSi > -0.0001 && angSi < 0.0001) { + double angCo = dot (prev, next); + if (angCo > 0.9999) { + // tout droit + stNo[LEFT] = enNo[LEFT] = dest->AddPoint(pos + width * pnor); + stNo[RIGHT] = enNo[RIGHT] = dest->AddPoint(pos - width * pnor); + } else { + // demi-tour + const Geom::Point sx = pos + width * pnor; + const Geom::Point ex = pos - width * pnor; + stNo[LEFT] = enNo[RIGHT] = dest->AddPoint (sx); + stNo[RIGHT] = enNo[LEFT] = dest->AddPoint (ex); + if (join == join_round) { + RecRound (dest, enNo[LEFT], stNo[LEFT], ex, sx, -pnor, pnor, pos, width); + dest->AddEdge(stNo[RIGHT], enNo[RIGHT]); + } else { + dest->AddEdge(enNo[LEFT], stNo[LEFT]); + dest->AddEdge(stNo[RIGHT], enNo[RIGHT]); // two times because both are crossing each other + } + } + return; + } + + if (angSi < 0) { + int midNo = dest->AddPoint(pos); + stNo[LEFT] = dest->AddPoint(pos + width * pnor); + enNo[LEFT] = dest->AddPoint(pos + width * nnor); + dest->AddEdge(enNo[LEFT], midNo); + dest->AddEdge(midNo, stNo[LEFT]); + + if (join == join_pointy) { + + stNo[RIGHT] = dest->AddPoint(pos - width * pnor); + enNo[RIGHT] = dest->AddPoint(pos - width * nnor); + + const Geom::Point biss = StrokeNormalize(prev - next); + double c2 = dot(biss, nnor); + double l = width / c2; + double emiter = width * c2; + if (emiter < miter) { + emiter = miter; + } + + if (fabs(l) < miter) { + int const n = dest->AddPoint(pos - l * biss); + dest->AddEdge(stNo[RIGHT], n); + dest->AddEdge(n, enNo[RIGHT]); + } else { + dest->AddEdge(stNo[RIGHT], enNo[RIGHT]); + } + + } else if (join == join_round) { + Geom::Point sx = pos - width * pnor; + stNo[RIGHT] = dest->AddPoint(sx); + Geom::Point ex = pos - width * nnor; + enNo[RIGHT] = dest->AddPoint(ex); + + RecRound(dest, stNo[RIGHT], enNo[RIGHT], + sx, ex, -pnor, -nnor, pos, width); + + } else { + stNo[RIGHT] = dest->AddPoint(pos - width * pnor); + enNo[RIGHT] = dest->AddPoint(pos - width * nnor); + dest->AddEdge(stNo[RIGHT], enNo[RIGHT]); + } + + } else { + + int midNo = dest->AddPoint(pos); + stNo[RIGHT] = dest->AddPoint(pos - width * pnor); + enNo[RIGHT] = dest->AddPoint(pos - width * nnor); + dest->AddEdge(stNo[RIGHT], midNo); + dest->AddEdge(midNo, enNo[RIGHT]); + + if (join == join_pointy) { + + stNo[LEFT] = dest->AddPoint(pos + width * pnor); + enNo[LEFT] = dest->AddPoint(pos + width * nnor); + + const Geom::Point biss = StrokeNormalize(next - prev); + double c2 = dot(biss, nnor); + double l = width / c2; + double emiter = width * c2; + if (emiter < miter) { + emiter = miter; + } + if ( fabs(l) < miter) { + int const n = dest->AddPoint (pos + l * biss); + dest->AddEdge (enNo[LEFT], n); + dest->AddEdge (n, stNo[LEFT]); + } + else + { + dest->AddEdge (enNo[LEFT], stNo[LEFT]); + } + + } else if (join == join_round) { + + Geom::Point sx = pos + width * pnor; + stNo[LEFT] = dest->AddPoint(sx); + Geom::Point ex = pos + width * nnor; + enNo[LEFT] = dest->AddPoint(ex); + + RecRound(dest, enNo[LEFT], stNo[LEFT], + ex, sx, nnor, pnor, pos, width); + + } else { + stNo[LEFT] = dest->AddPoint(pos + width * pnor); + enNo[LEFT] = dest->AddPoint(pos + width * nnor); + dest->AddEdge(enNo[LEFT], stNo[LEFT]); + } + } +} + + void +Path::DoLeftJoin (Shape * dest, double width, JoinType join, Geom::Point pos, + Geom::Point prev, Geom::Point next, double miter, double /*prevL*/, double /*nextL*/, + int &leftStNo, int &leftEnNo,int pathID,int pieceID,double tID) +{ + Geom::Point pnor=prev.ccw(); + Geom::Point nnor=next.ccw(); + double angSi = cross(prev, next); + if (angSi > -0.0001 && angSi < 0.0001) + { + double angCo = dot (prev, next); + if (angCo > 0.9999) + { + // tout droit + leftEnNo = leftStNo = dest->AddPoint (pos + width * pnor); + } + else + { + // demi-tour + leftStNo = dest->AddPoint (pos + width * pnor); + leftEnNo = dest->AddPoint (pos - width * pnor); + int nEdge=dest->AddEdge (leftEnNo, leftStNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + } + return; + } + if (angSi < 0) + { + /* Geom::Point biss; + biss.x=next.x-prev.x; + biss.y=next.y-prev.y; + double c2=cross(next, biss); + double l=width/c2; + double projn=l*(dot(biss,next)); + double projp=-l*(dot(biss,prev)); + if ( projp <= 0.5*prevL && projn <= 0.5*nextL ) { + double x,y; + x=pos.x+l*biss.x; + y=pos.y+l*biss.y; + leftEnNo=leftStNo=dest->AddPoint(x,y); + } else {*/ + leftStNo = dest->AddPoint (pos + width * pnor); + leftEnNo = dest->AddPoint (pos + width * nnor); +// int midNo = dest->AddPoint (pos); +// int nEdge=dest->AddEdge (leftEnNo, midNo); + int nEdge=dest->AddEdge (leftEnNo, leftStNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } +// nEdge=dest->AddEdge (midNo, leftStNo); +// if ( dest->hasBackData() ) { +// dest->ebData[nEdge].pathID=pathID; +// dest->ebData[nEdge].pieceID=pieceID; +// dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; +// } + // } + } + else + { + if (join == join_pointy) + { + leftStNo = dest->AddPoint (pos + width * pnor); + leftEnNo = dest->AddPoint (pos + width * nnor); + + const Geom::Point biss = StrokeNormalize (pnor + nnor); + double c2 = dot (biss, nnor); + double l = width / c2; + double emiter = width * c2; + if (emiter < miter) + emiter = miter; + if (l <= emiter) + { + int nleftStNo = dest->AddPoint (pos + l * biss); + int nEdge=dest->AddEdge (leftEnNo, nleftStNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + nEdge=dest->AddEdge (nleftStNo, leftStNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + } + else + { + double s2 = cross(nnor, biss); + double dec = (l - emiter) * c2 / s2; + const Geom::Point tbiss=biss.ccw(); + + int nleftStNo = dest->AddPoint (pos + emiter * biss + dec * tbiss); + int nleftEnNo = dest->AddPoint (pos + emiter * biss - dec * tbiss); + int nEdge=dest->AddEdge (nleftEnNo, nleftStNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + nEdge=dest->AddEdge (leftEnNo, nleftEnNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + nEdge=dest->AddEdge (nleftStNo, leftStNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + } + } + else if (join == join_round) + { + const Geom::Point sx = pos + width * pnor; + leftStNo = dest->AddPoint (sx); + const Geom::Point ex = pos + width * nnor; + leftEnNo = dest->AddPoint (ex); + + RecRound(dest, leftEnNo, leftStNo, + sx, ex, pnor, nnor ,pos, width); + + } + else + { + leftStNo = dest->AddPoint (pos + width * pnor); + leftEnNo = dest->AddPoint (pos + width * nnor); + int nEdge=dest->AddEdge (leftEnNo, leftStNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + } + } +} + void +Path::DoRightJoin (Shape * dest, double width, JoinType join, Geom::Point pos, + Geom::Point prev, Geom::Point next, double miter, double /*prevL*/, + double /*nextL*/, int &rightStNo, int &rightEnNo,int pathID,int pieceID,double tID) +{ + const Geom::Point pnor=prev.ccw(); + const Geom::Point nnor=next.ccw(); + double angSi = cross(prev, next); + if (angSi > -0.0001 && angSi < 0.0001) + { + double angCo = dot (prev, next); + if (angCo > 0.9999) + { + // tout droit + rightEnNo = rightStNo = dest->AddPoint (pos - width*pnor); + } + else + { + // demi-tour + rightEnNo = dest->AddPoint (pos + width*pnor); + rightStNo = dest->AddPoint (pos - width*pnor); + int nEdge=dest->AddEdge (rightStNo, rightEnNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + } + return; + } + if (angSi < 0) + { + if (join == join_pointy) + { + rightStNo = dest->AddPoint (pos - width*pnor); + rightEnNo = dest->AddPoint (pos - width*nnor); + + const Geom::Point biss = StrokeNormalize (pnor + nnor); + double c2 = dot (biss, nnor); + double l = width / c2; + double emiter = width * c2; + if (emiter < miter) + emiter = miter; + if (l <= emiter) + { + int nrightStNo = dest->AddPoint (pos - l * biss); + int nEdge=dest->AddEdge (rightStNo, nrightStNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + nEdge=dest->AddEdge (nrightStNo, rightEnNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + } + else + { + double s2 = cross(nnor, biss); + double dec = (l - emiter) * c2 / s2; + const Geom::Point tbiss=biss.ccw(); + + int nrightStNo = dest->AddPoint (pos - emiter*biss - dec*tbiss); + int nrightEnNo = dest->AddPoint (pos - emiter*biss + dec*tbiss); + int nEdge=dest->AddEdge (rightStNo, nrightStNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + nEdge=dest->AddEdge (nrightStNo, nrightEnNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + nEdge=dest->AddEdge (nrightEnNo, rightEnNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + } + } + else if (join == join_round) + { + const Geom::Point sx = pos - width * pnor; + rightStNo = dest->AddPoint (sx); + const Geom::Point ex = pos - width * nnor; + rightEnNo = dest->AddPoint (ex); + + RecRound(dest, rightStNo, rightEnNo, + sx, ex, -pnor, -nnor ,pos, width); + } + else + { + rightStNo = dest->AddPoint (pos - width * pnor); + rightEnNo = dest->AddPoint (pos - width * nnor); + int nEdge=dest->AddEdge (rightStNo, rightEnNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } + } + } + else + { + /* Geom::Point biss; + biss=next.x-prev.x; + biss.y=next.y-prev.y; + double c2=cross(biss, next); + double l=width/c2; + double projn=l*(dot(biss,next)); + double projp=-l*(dot(biss,prev)); + if ( projp <= 0.5*prevL && projn <= 0.5*nextL ) { + double x,y; + x=pos.x+l*biss.x; + y=pos.y+l*biss.y; + rightEnNo=rightStNo=dest->AddPoint(x,y); + } else {*/ + rightStNo = dest->AddPoint (pos - width*pnor); + rightEnNo = dest->AddPoint (pos - width*nnor); +// int midNo = dest->AddPoint (pos); +// int nEdge=dest->AddEdge (rightStNo, midNo); + int nEdge=dest->AddEdge (rightStNo, rightEnNo); + if ( dest->hasBackData() ) { + dest->ebData[nEdge].pathID=pathID; + dest->ebData[nEdge].pieceID=pieceID; + dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; + } +// nEdge=dest->AddEdge (midNo, rightEnNo); +// if ( dest->hasBackData() ) { +// dest->ebData[nEdge].pathID=pathID; +// dest->ebData[nEdge].pieceID=pieceID; +// dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; +// } + // } + } +} + + +// a very ugly way to produce round joins: doing one (or two, depend on the angle of the join) quadratic bezier curves +// but since most joins are going to be small, nobody will notice -- but somebody noticed and now the ugly stuff is gone! so: + +// a very nice way to produce round joins, caps or dots +void Path::RecRound(Shape *dest, int sNo, int eNo, // start and end index + Geom::Point const &iS, Geom::Point const &iE, // start and end point + Geom::Point const &nS, Geom::Point const &nE, // start and end normal vector + Geom::Point &origine, float width) // center and radius of round +{ + //Geom::Point diff = iS - iE; + //double dist = dot(diff, diff); + if (width < 0.5 || dot(iS - iE, iS - iE)/width < 2.0) { + dest->AddEdge(sNo, eNo); + return; + } + double ang, sia, lod; + if (nS == -nE) { + ang = M_PI; + sia = 1; + } else { + double coa = dot(nS, nE); + sia = cross(nE, nS); + ang = acos(coa); + if ( coa >= 1 ) { + ang = 0; + } + if ( coa <= -1 ) { + ang = M_PI; + } + } + lod = 0.02 + 10 / (10 + width); // limit detail to about 2 degrees (180 * 0.02/Pi degrees) + ang /= lod; + + int nbS = (int) floor(ang); + Geom::Rotate omega(((sia > 0) ? -lod : lod)); + Geom::Point cur = iS - origine; + // StrokeNormalize(cur); + // cur*=width; + int lastNo = sNo; + for (int i = 0; i < nbS; i++) { + cur = cur * omega; + Geom::Point m = origine + cur; + int mNo = dest->AddPoint(m); + dest->AddEdge(lastNo, mNo); + lastNo = mNo; + } + dest->AddEdge(lastNo, eNo); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/README b/src/livarot/README new file mode 100644 index 0000000..3e6f138 --- /dev/null +++ b/src/livarot/README @@ -0,0 +1,17 @@ + + +This directory contains path and shape related code. This code is +partially replaced by lib2geom. We still rely on this code for: + +* Binary path operations. +* Path offsetting. +* Finding start and end positions for lines text inside a shape. + +To do: + +* Move needed functionality to lib2geom or independent functions. +* Delete directory. + +(Livarot is a pungent French cow milk cheese.) + + diff --git a/src/livarot/Shape.cpp b/src/livarot/Shape.cpp new file mode 100644 index 0000000..0702534 --- /dev/null +++ b/src/livarot/Shape.cpp @@ -0,0 +1,2347 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cstdio> +#include <cstdlib> +#include <glib.h> +#include "Shape.h" +#include "livarot/sweep-event-queue.h" +#include "livarot/sweep-tree-list.h" + +/* + * Shape instances handling. + * never (i repeat: never) modify edges and points links; use Connect() and Disconnect() instead + * the graph is stored as a set of points and edges, with edges in a doubly-linked list for each point. + */ + +Shape::Shape() + : nbQRas(0), + firstQRas(-1), + lastQRas(-1), + qrsData(nullptr), + nbInc(0), + maxInc(0), + iData(nullptr), + sTree(nullptr), + sEvts(nullptr), + _need_points_sorting(false), + _need_edges_sorting(false), + _has_points_data(false), + _point_data_initialised(false), + _has_edges_data(false), + _has_sweep_src_data(false), + _has_sweep_dest_data(false), + _has_raster_data(false), + _has_quick_raster_data(false), + _has_back_data(false), + _has_voronoi_data(false), + _bbox_up_to_date(false) +{ + leftX = topY = rightX = bottomY = 0; + maxPt = 0; + maxAr = 0; + + type = shape_polygon; +} +Shape::~Shape () +{ + maxPt = 0; + maxAr = 0; + free(qrsData); +} + +void Shape::Affiche() +{ + printf("sh=%p nbPt=%i nbAr=%i\n", this, static_cast<int>(_pts.size()), static_cast<int>(_aretes.size())); // localizing ok + for (unsigned int i=0; i<_pts.size(); i++) { + printf("pt %u : x=(%f %f) dI=%i dO=%i\n",i, _pts[i].x[0], _pts[i].x[1], _pts[i].dI, _pts[i].dO); // localizing ok + } + for (unsigned int i=0; i<_aretes.size(); i++) { + printf("ar %u : dx=(%f %f) st=%i en=%i\n",i, _aretes[i].dx[0], _aretes[i].dx[1], _aretes[i].st, _aretes[i].en); // localizing ok + } +} + +/** + * Allocates space for point cache or clears the cache + \param nVal Allocate a cache (true) or clear it (false) + */ +void +Shape::MakePointData (bool nVal) +{ + if (nVal) + { + if (_has_points_data == false) + { + _has_points_data = true; + _point_data_initialised = false; + _bbox_up_to_date = false; + pData.resize(maxPt); + } + } + /* no need to clean point data - keep it cached*/ +} + +void +Shape::MakeEdgeData (bool nVal) +{ + if (nVal) + { + if (_has_edges_data == false) + { + _has_edges_data = true; + eData.resize(maxAr); + } + } + else + { + if (_has_edges_data) + { + _has_edges_data = false; + eData.clear(); + } + } +} + +void +Shape::MakeRasterData (bool nVal) +{ + if (nVal) + { + if (_has_raster_data == false) + { + _has_raster_data = true; + swrData.resize(maxAr); + } + } + else + { + if (_has_raster_data) + { + _has_raster_data = false; + swrData.clear(); + } + } +} +void +Shape::MakeQuickRasterData (bool nVal) +{ + if (nVal) + { + if (_has_quick_raster_data == false) + { + _has_quick_raster_data = true; + quick_raster_data* new_qrsData = static_cast<quick_raster_data*>(realloc(qrsData, maxAr * sizeof(quick_raster_data))); + if (!new_qrsData) { + g_error("Not enough memory available for reallocating Shape::qrsData"); + } else { + qrsData = new_qrsData; + } + } + } + else + { + if (_has_quick_raster_data) + { + _has_quick_raster_data = false; + } + } +} +void +Shape::MakeSweepSrcData (bool nVal) +{ + if (nVal) + { + if (_has_sweep_src_data == false) + { + _has_sweep_src_data = true; + swsData.resize(maxAr); + } + } + else + { + if (_has_sweep_src_data) + { + _has_sweep_src_data = false; + swsData.clear(); + } + } +} +void +Shape::MakeSweepDestData (bool nVal) +{ + if (nVal) + { + if (_has_sweep_dest_data == false) + { + _has_sweep_dest_data = true; + swdData.resize(maxAr); + } + } + else + { + if (_has_sweep_dest_data) + { + _has_sweep_dest_data = false; + swdData.clear(); + } + } +} +void +Shape::MakeBackData (bool nVal) +{ + if (nVal) + { + if (_has_back_data == false) + { + _has_back_data = true; + ebData.resize(maxAr); + } + } + else + { + if (_has_back_data) + { + _has_back_data = false; + ebData.clear(); + } + } +} +void +Shape::MakeVoronoiData (bool nVal) +{ + if (nVal) + { + if (_has_voronoi_data == false) + { + _has_voronoi_data = true; + vorpData.resize(maxPt); + voreData.resize(maxAr); + } + } + else + { + if (_has_voronoi_data) + { + _has_voronoi_data = false; + vorpData.clear(); + voreData.clear(); + } + } +} + + +/** + * Copy point and edge data from `who' into this object, discarding + * any cached data that we have. + */ +void +Shape::Copy (Shape * who) +{ + if (who == nullptr) + { + Reset (0, 0); + return; + } + MakePointData (false); + MakeEdgeData (false); + MakeSweepSrcData (false); + MakeSweepDestData (false); + MakeRasterData (false); + MakeQuickRasterData (false); + MakeBackData (false); + + delete sTree; + sTree = nullptr; + delete sEvts; + sEvts = nullptr; + + Reset (who->numberOfPoints(), who->numberOfEdges()); + type = who->type; + _need_points_sorting = who->_need_points_sorting; + _need_edges_sorting = who->_need_edges_sorting; + _has_points_data = false; + _point_data_initialised = false; + _has_edges_data = false; + _has_sweep_src_data = false; + _has_sweep_dest_data = false; + _has_raster_data = false; + _has_quick_raster_data = false; + _has_back_data = false; + _has_voronoi_data = false; + _bbox_up_to_date = false; + + _pts = who->_pts; + _aretes = who->_aretes; +} + +/** + * Clear points and edges and prepare internal data using new size. + */ +void +Shape::Reset (int pointCount, int edgeCount) +{ + _pts.clear(); + _aretes.clear(); + + type = shape_polygon; + if (pointCount > maxPt) + { + maxPt = pointCount; + if (_has_points_data) + pData.resize(maxPt); + if (_has_voronoi_data) + vorpData.resize(maxPt); + } + if (edgeCount > maxAr) + { + maxAr = edgeCount; + if (_has_edges_data) + eData.resize(maxAr); + if (_has_sweep_dest_data) + swdData.resize(maxAr); + if (_has_sweep_src_data) + swsData.resize(maxAr); + if (_has_back_data) + ebData.resize(maxAr); + if (_has_voronoi_data) + voreData.resize(maxAr); + } + _need_points_sorting = false; + _need_edges_sorting = false; + _point_data_initialised = false; + _bbox_up_to_date = false; +} + +int +Shape::AddPoint (const Geom::Point x) +{ + if (numberOfPoints() >= maxPt) + { + maxPt = 2 * numberOfPoints() + 1; + if (_has_points_data) + pData.resize(maxPt); + if (_has_voronoi_data) + vorpData.resize(maxPt); + } + + dg_point p; + p.x = x; + p.dI = p.dO = 0; + p.incidentEdge[FIRST] = p.incidentEdge[LAST] = -1; + p.oldDegree = -1; + _pts.push_back(p); + int const n = _pts.size() - 1; + + if (_has_points_data) + { + pData[n].pending = 0; + pData[n].edgeOnLeft = -1; + pData[n].nextLinkedPoint = -1; + pData[n].askForWindingS = nullptr; + pData[n].askForWindingB = -1; + pData[n].rx[0] = Round(p.x[0]); + pData[n].rx[1] = Round(p.x[1]); + } + if (_has_voronoi_data) + { + vorpData[n].value = 0.0; + vorpData[n].winding = -2; + } + _need_points_sorting = true; + + return n; +} + +void +Shape::SubPoint (int p) +{ + if (p < 0 || p >= numberOfPoints()) + return; + _need_points_sorting = true; + int cb; + cb = getPoint(p).incidentEdge[FIRST]; + while (cb >= 0 && cb < numberOfEdges()) + { + if (getEdge(cb).st == p) + { + int ncb = getEdge(cb).nextS; + _aretes[cb].nextS = _aretes[cb].prevS = -1; + _aretes[cb].st = -1; + cb = ncb; + } + else if (getEdge(cb).en == p) + { + int ncb = getEdge(cb).nextE; + _aretes[cb].nextE = _aretes[cb].prevE = -1; + _aretes[cb].en = -1; + cb = ncb; + } + else + { + break; + } + } + _pts[p].incidentEdge[FIRST] = _pts[p].incidentEdge[LAST] = -1; + if (p < numberOfPoints() - 1) + SwapPoints (p, numberOfPoints() - 1); + _pts.pop_back(); +} + +void +Shape::SwapPoints (int a, int b) +{ + if (a == b) + return; + if (getPoint(a).totalDegree() == 2 && getPoint(b).totalDegree() == 2) + { + int cb = getPoint(a).incidentEdge[FIRST]; + if (getEdge(cb).st == a) + { + _aretes[cb].st = numberOfPoints(); + } + else if (getEdge(cb).en == a) + { + _aretes[cb].en = numberOfPoints(); + } + cb = getPoint(a).incidentEdge[LAST]; + if (getEdge(cb).st == a) + { + _aretes[cb].st = numberOfPoints(); + } + else if (getEdge(cb).en == a) + { + _aretes[cb].en = numberOfPoints(); + } + + cb = getPoint(b).incidentEdge[FIRST]; + if (getEdge(cb).st == b) + { + _aretes[cb].st = a; + } + else if (getEdge(cb).en == b) + { + _aretes[cb].en = a; + } + cb = getPoint(b).incidentEdge[LAST]; + if (getEdge(cb).st == b) + { + _aretes[cb].st = a; + } + else if (getEdge(cb).en == b) + { + _aretes[cb].en = a; + } + + cb = getPoint(a).incidentEdge[FIRST]; + if (getEdge(cb).st == numberOfPoints()) + { + _aretes[cb].st = b; + } + else if (getEdge(cb).en == numberOfPoints()) + { + _aretes[cb].en = b; + } + cb = getPoint(a).incidentEdge[LAST]; + if (getEdge(cb).st == numberOfPoints()) + { + _aretes[cb].st = b; + } + else if (getEdge(cb).en == numberOfPoints()) + { + _aretes[cb].en = b; + } + + } + else + { + int cb; + cb = getPoint(a).incidentEdge[FIRST]; + while (cb >= 0) + { + int ncb = NextAt (a, cb); + if (getEdge(cb).st == a) + { + _aretes[cb].st = numberOfPoints(); + } + else if (getEdge(cb).en == a) + { + _aretes[cb].en = numberOfPoints(); + } + cb = ncb; + } + cb = getPoint(b).incidentEdge[FIRST]; + while (cb >= 0) + { + int ncb = NextAt (b, cb); + if (getEdge(cb).st == b) + { + _aretes[cb].st = a; + } + else if (getEdge(cb).en == b) + { + _aretes[cb].en = a; + } + cb = ncb; + } + cb = getPoint(a).incidentEdge[FIRST]; + while (cb >= 0) + { + int ncb = NextAt (numberOfPoints(), cb); + if (getEdge(cb).st == numberOfPoints()) + { + _aretes[cb].st = b; + } + else if (getEdge(cb).en == numberOfPoints()) + { + _aretes[cb].en = b; + } + cb = ncb; + } + } + { + dg_point swap = getPoint(a); + _pts[a] = getPoint(b); + _pts[b] = swap; + } + if (_has_points_data) + { + point_data swad = pData[a]; + pData[a] = pData[b]; + pData[b] = swad; + // pData[pData[a].oldInd].newInd=a; + // pData[pData[b].oldInd].newInd=b; + } + if (_has_voronoi_data) + { + voronoi_point swav = vorpData[a]; + vorpData[a] = vorpData[b]; + vorpData[b] = swav; + } +} +void +Shape::SwapPoints (int a, int b, int c) +{ + if (a == b || b == c || a == c) + return; + SwapPoints (a, b); + SwapPoints (b, c); +} + +void +Shape::SortPoints () +{ + if (_need_points_sorting && hasPoints()) + SortPoints (0, numberOfPoints() - 1); + _need_points_sorting = false; +} + +void +Shape::SortPointsRounded () +{ + if (hasPoints()) + SortPointsRounded (0, numberOfPoints() - 1); +} + +void +Shape::SortPoints (int s, int e) +{ + if (s >= e) + return; + if (e == s + 1) + { + if (getPoint(s).x[1] > getPoint(e).x[1] + || (getPoint(s).x[1] == getPoint(e).x[1] && getPoint(s).x[0] > getPoint(e).x[0])) + SwapPoints (s, e); + return; + } + + int ppos = (s + e) / 2; + int plast = ppos; + double pvalx = getPoint(ppos).x[0]; + double pvaly = getPoint(ppos).x[1]; + + int le = s, ri = e; + while (le < ppos || ri > plast) + { + if (le < ppos) + { + do + { + int test = 0; + if (getPoint(le).x[1] > pvaly) + { + test = 1; + } + else if (getPoint(le).x[1] == pvaly) + { + if (getPoint(le).x[0] > pvalx) + { + test = 1; + } + else if (getPoint(le).x[0] == pvalx) + { + test = 0; + } + else + { + test = -1; + } + } + else + { + test = -1; + } + if (test == 0) + { + // on colle les valeurs egales au pivot ensemble + if (le < ppos - 1) + { + SwapPoints (le, ppos - 1, ppos); + ppos--; + continue; // sans changer le + } + else if (le == ppos - 1) + { + ppos--; + break; + } + else + { + // oupsie + break; + } + } + if (test > 0) + { + break; + } + le++; + } + while (le < ppos); + } + if (ri > plast) + { + do + { + int test = 0; + if (getPoint(ri).x[1] > pvaly) + { + test = 1; + } + else if (getPoint(ri).x[1] == pvaly) + { + if (getPoint(ri).x[0] > pvalx) + { + test = 1; + } + else if (getPoint(ri).x[0] == pvalx) + { + test = 0; + } + else + { + test = -1; + } + } + else + { + test = -1; + } + if (test == 0) + { + // on colle les valeurs egales au pivot ensemble + if (ri > plast + 1) + { + SwapPoints (ri, plast + 1, plast); + plast++; + continue; // sans changer ri + } + else if (ri == plast + 1) + { + plast++; + break; + } + else + { + // oupsie + break; + } + } + if (test < 0) + { + break; + } + ri--; + } + while (ri > plast); + } + if (le < ppos) + { + if (ri > plast) + { + SwapPoints (le, ri); + le++; + ri--; + } + else + { + if (le < ppos - 1) + { + SwapPoints (ppos - 1, plast, le); + ppos--; + plast--; + } + else if (le == ppos - 1) + { + SwapPoints (plast, le); + ppos--; + plast--; + } + } + } + else + { + if (ri > plast + 1) + { + SwapPoints (plast + 1, ppos, ri); + ppos++; + plast++; + } + else if (ri == plast + 1) + { + SwapPoints (ppos, ri); + ppos++; + plast++; + } + else + { + break; + } + } + } + SortPoints (s, ppos - 1); + SortPoints (plast + 1, e); +} + +void +Shape::SortPointsByOldInd (int s, int e) +{ + if (s >= e) + return; + if (e == s + 1) + { + if (getPoint(s).x[1] > getPoint(e).x[1] || (getPoint(s).x[1] == getPoint(e).x[1] && getPoint(s).x[0] > getPoint(e).x[0]) + || (getPoint(s).x[1] == getPoint(e).x[1] && getPoint(s).x[0] == getPoint(e).x[0] + && pData[s].oldInd > pData[e].oldInd)) + SwapPoints (s, e); + return; + } + + int ppos = (s + e) / 2; + int plast = ppos; + double pvalx = getPoint(ppos).x[0]; + double pvaly = getPoint(ppos).x[1]; + int pvali = pData[ppos].oldInd; + + int le = s, ri = e; + while (le < ppos || ri > plast) + { + if (le < ppos) + { + do + { + int test = 0; + if (getPoint(le).x[1] > pvaly) + { + test = 1; + } + else if (getPoint(le).x[1] == pvaly) + { + if (getPoint(le).x[0] > pvalx) + { + test = 1; + } + else if (getPoint(le).x[0] == pvalx) + { + if (pData[le].oldInd > pvali) + { + test = 1; + } + else if (pData[le].oldInd == pvali) + { + test = 0; + } + else + { + test = -1; + } + } + else + { + test = -1; + } + } + else + { + test = -1; + } + if (test == 0) + { + // on colle les valeurs egales au pivot ensemble + if (le < ppos - 1) + { + SwapPoints (le, ppos - 1, ppos); + ppos--; + continue; // sans changer le + } + else if (le == ppos - 1) + { + ppos--; + break; + } + else + { + // oupsie + break; + } + } + if (test > 0) + { + break; + } + le++; + } + while (le < ppos); + } + if (ri > plast) + { + do + { + int test = 0; + if (getPoint(ri).x[1] > pvaly) + { + test = 1; + } + else if (getPoint(ri).x[1] == pvaly) + { + if (getPoint(ri).x[0] > pvalx) + { + test = 1; + } + else if (getPoint(ri).x[0] == pvalx) + { + if (pData[ri].oldInd > pvali) + { + test = 1; + } + else if (pData[ri].oldInd == pvali) + { + test = 0; + } + else + { + test = -1; + } + } + else + { + test = -1; + } + } + else + { + test = -1; + } + if (test == 0) + { + // on colle les valeurs egales au pivot ensemble + if (ri > plast + 1) + { + SwapPoints (ri, plast + 1, plast); + plast++; + continue; // sans changer ri + } + else if (ri == plast + 1) + { + plast++; + break; + } + else + { + // oupsie + break; + } + } + if (test < 0) + { + break; + } + ri--; + } + while (ri > plast); + } + if (le < ppos) + { + if (ri > plast) + { + SwapPoints (le, ri); + le++; + ri--; + } + else + { + if (le < ppos - 1) + { + SwapPoints (ppos - 1, plast, le); + ppos--; + plast--; + } + else if (le == ppos - 1) + { + SwapPoints (plast, le); + ppos--; + plast--; + } + } + } + else + { + if (ri > plast + 1) + { + SwapPoints (plast + 1, ppos, ri); + ppos++; + plast++; + } + else if (ri == plast + 1) + { + SwapPoints (ppos, ri); + ppos++; + plast++; + } + else + { + break; + } + } + } + SortPointsByOldInd (s, ppos - 1); + SortPointsByOldInd (plast + 1, e); +} + +void +Shape::SortPointsRounded (int s, int e) +{ + if (s >= e) + return; + if (e == s + 1) + { + if (pData[s].rx[1] > pData[e].rx[1] + || (pData[s].rx[1] == pData[e].rx[1] && pData[s].rx[0] > pData[e].rx[0])) + SwapPoints (s, e); + return; + } + + int ppos = (s + e) / 2; + int plast = ppos; + double pvalx = pData[ppos].rx[0]; + double pvaly = pData[ppos].rx[1]; + + int le = s, ri = e; + while (le < ppos || ri > plast) + { + if (le < ppos) + { + do + { + int test = 0; + if (pData[le].rx[1] > pvaly) + { + test = 1; + } + else if (pData[le].rx[1] == pvaly) + { + if (pData[le].rx[0] > pvalx) + { + test = 1; + } + else if (pData[le].rx[0] == pvalx) + { + test = 0; + } + else + { + test = -1; + } + } + else + { + test = -1; + } + if (test == 0) + { + // on colle les valeurs egales au pivot ensemble + if (le < ppos - 1) + { + SwapPoints (le, ppos - 1, ppos); + ppos--; + continue; // sans changer le + } + else if (le == ppos - 1) + { + ppos--; + break; + } + else + { + // oupsie + break; + } + } + if (test > 0) + { + break; + } + le++; + } + while (le < ppos); + } + if (ri > plast) + { + do + { + int test = 0; + if (pData[ri].rx[1] > pvaly) + { + test = 1; + } + else if (pData[ri].rx[1] == pvaly) + { + if (pData[ri].rx[0] > pvalx) + { + test = 1; + } + else if (pData[ri].rx[0] == pvalx) + { + test = 0; + } + else + { + test = -1; + } + } + else + { + test = -1; + } + if (test == 0) + { + // on colle les valeurs egales au pivot ensemble + if (ri > plast + 1) + { + SwapPoints (ri, plast + 1, plast); + plast++; + continue; // sans changer ri + } + else if (ri == plast + 1) + { + plast++; + break; + } + else + { + // oupsie + break; + } + } + if (test < 0) + { + break; + } + ri--; + } + while (ri > plast); + } + if (le < ppos) + { + if (ri > plast) + { + SwapPoints (le, ri); + le++; + ri--; + } + else + { + if (le < ppos - 1) + { + SwapPoints (ppos - 1, plast, le); + ppos--; + plast--; + } + else if (le == ppos - 1) + { + SwapPoints (plast, le); + ppos--; + plast--; + } + } + } + else + { + if (ri > plast + 1) + { + SwapPoints (plast + 1, ppos, ri); + ppos++; + plast++; + } + else if (ri == plast + 1) + { + SwapPoints (ppos, ri); + ppos++; + plast++; + } + else + { + break; + } + } + } + SortPointsRounded (s, ppos - 1); + SortPointsRounded (plast + 1, e); +} + +/* + * + */ +int +Shape::AddEdge (int st, int en) +{ + if (st == en) + return -1; + if (st < 0 || en < 0) + return -1; + type = shape_graph; + if (numberOfEdges() >= maxAr) + { + maxAr = 2 * numberOfEdges() + 1; + if (_has_edges_data) + eData.resize(maxAr); + if (_has_sweep_src_data) + swsData.resize(maxAr); + if (_has_sweep_dest_data) + swdData.resize(maxAr); + if (_has_raster_data) + swrData.resize(maxAr); + if (_has_back_data) + ebData.resize(maxAr); + if (_has_voronoi_data) + voreData.resize(maxAr); + } + + dg_arete a; + a.dx = Geom::Point(0, 0); + a.st = a.en = -1; + a.prevS = a.nextS = -1; + a.prevE = a.nextE = -1; + if (st >= 0 && en >= 0) { + a.dx = getPoint(en).x - getPoint(st).x; + } + + _aretes.push_back(a); + int const n = numberOfEdges() - 1; + + ConnectStart (st, n); + ConnectEnd (en, n); + if (_has_edges_data) + { + eData[n].weight = 1; + eData[n].rdx = getEdge(n).dx; + } + if (_has_sweep_src_data) + { + swsData[n].misc = nullptr; + swsData[n].firstLinkedPoint = -1; + } + if (_has_back_data) + { + ebData[n].pathID = -1; + ebData[n].pieceID = -1; + ebData[n].tSt = ebData[n].tEn = 0; + } + if (_has_voronoi_data) + { + voreData[n].leF = -1; + voreData[n].riF = -1; + } + _need_edges_sorting = true; + return n; +} + +int +Shape::AddEdge (int st, int en, int leF, int riF) +{ + if (st == en) + return -1; + if (st < 0 || en < 0) + return -1; + { + int cb = getPoint(st).incidentEdge[FIRST]; + while (cb >= 0) + { + if (getEdge(cb).st == st && getEdge(cb).en == en) + return -1; // doublon + if (getEdge(cb).st == en && getEdge(cb).en == st) + return -1; // doublon + cb = NextAt (st, cb); + } + } + type = shape_graph; + if (numberOfEdges() >= maxAr) + { + maxAr = 2 * numberOfEdges() + 1; + if (_has_edges_data) + eData.resize(maxAr); + if (_has_sweep_src_data) + swsData.resize(maxAr); + if (_has_sweep_dest_data) + swdData.resize(maxAr); + if (_has_raster_data) + swrData.resize(maxAr); + if (_has_back_data) + ebData.resize(maxAr); + if (_has_voronoi_data) + voreData.resize(maxAr); + } + + dg_arete a; + a.dx = Geom::Point(0, 0); + a.st = a.en = -1; + a.prevS = a.nextS = -1; + a.prevE = a.nextE = -1; + if (st >= 0 && en >= 0) { + a.dx = getPoint(en).x - getPoint(st).x; + } + + _aretes.push_back(a); + int const n = numberOfEdges() - 1; + + ConnectStart (st, n); + ConnectEnd (en, n); + if (_has_edges_data) + { + eData[n].weight = 1; + eData[n].rdx = getEdge(n).dx; + } + if (_has_sweep_src_data) + { + swsData[n].misc = nullptr; + swsData[n].firstLinkedPoint = -1; + } + if (_has_back_data) + { + ebData[n].pathID = -1; + ebData[n].pieceID = -1; + ebData[n].tSt = ebData[n].tEn = 0; + } + if (_has_voronoi_data) + { + voreData[n].leF = leF; + voreData[n].riF = riF; + } + _need_edges_sorting = true; + return n; +} + +void +Shape::SubEdge (int e) +{ + if (e < 0 || e >= numberOfEdges()) + return; + type = shape_graph; + DisconnectStart (e); + DisconnectEnd (e); + if (e < numberOfEdges() - 1) + SwapEdges (e, numberOfEdges() - 1); + _aretes.pop_back(); + _need_edges_sorting = true; +} + +void +Shape::SwapEdges (int a, int b) +{ + if (a == b) + return; + if (getEdge(a).prevS >= 0 && getEdge(a).prevS != b) + { + if (getEdge(getEdge(a).prevS).st == getEdge(a).st) + { + _aretes[getEdge(a).prevS].nextS = b; + } + else if (getEdge(getEdge(a).prevS).en == getEdge(a).st) + { + _aretes[getEdge(a).prevS].nextE = b; + } + } + if (getEdge(a).nextS >= 0 && getEdge(a).nextS != b) + { + if (getEdge(getEdge(a).nextS).st == getEdge(a).st) + { + _aretes[getEdge(a).nextS].prevS = b; + } + else if (getEdge(getEdge(a).nextS).en == getEdge(a).st) + { + _aretes[getEdge(a).nextS].prevE = b; + } + } + if (getEdge(a).prevE >= 0 && getEdge(a).prevE != b) + { + if (getEdge(getEdge(a).prevE).st == getEdge(a).en) + { + _aretes[getEdge(a).prevE].nextS = b; + } + else if (getEdge(getEdge(a).prevE).en == getEdge(a).en) + { + _aretes[getEdge(a).prevE].nextE = b; + } + } + if (getEdge(a).nextE >= 0 && getEdge(a).nextE != b) + { + if (getEdge(getEdge(a).nextE).st == getEdge(a).en) + { + _aretes[getEdge(a).nextE].prevS = b; + } + else if (getEdge(getEdge(a).nextE).en == getEdge(a).en) + { + _aretes[getEdge(a).nextE].prevE = b; + } + } + if (getEdge(a).st >= 0) + { + if (getPoint(getEdge(a).st).incidentEdge[FIRST] == a) + _pts[getEdge(a).st].incidentEdge[FIRST] = numberOfEdges(); + if (getPoint(getEdge(a).st).incidentEdge[LAST] == a) + _pts[getEdge(a).st].incidentEdge[LAST] = numberOfEdges(); + } + if (getEdge(a).en >= 0) + { + if (getPoint(getEdge(a).en).incidentEdge[FIRST] == a) + _pts[getEdge(a).en].incidentEdge[FIRST] = numberOfEdges(); + if (getPoint(getEdge(a).en).incidentEdge[LAST] == a) + _pts[getEdge(a).en].incidentEdge[LAST] = numberOfEdges(); + } + + + if (getEdge(b).prevS >= 0 && getEdge(b).prevS != a) + { + if (getEdge(getEdge(b).prevS).st == getEdge(b).st) + { + _aretes[getEdge(b).prevS].nextS = a; + } + else if (getEdge(getEdge(b).prevS).en == getEdge(b).st) + { + _aretes[getEdge(b).prevS].nextE = a; + } + } + if (getEdge(b).nextS >= 0 && getEdge(b).nextS != a) + { + if (getEdge(getEdge(b).nextS).st == getEdge(b).st) + { + _aretes[getEdge(b).nextS].prevS = a; + } + else if (getEdge(getEdge(b).nextS).en == getEdge(b).st) + { + _aretes[getEdge(b).nextS].prevE = a; + } + } + if (getEdge(b).prevE >= 0 && getEdge(b).prevE != a) + { + if (getEdge(getEdge(b).prevE).st == getEdge(b).en) + { + _aretes[getEdge(b).prevE].nextS = a; + } + else if (getEdge(getEdge(b).prevE).en == getEdge(b).en) + { + _aretes[getEdge(b).prevE].nextE = a; + } + } + if (getEdge(b).nextE >= 0 && getEdge(b).nextE != a) + { + if (getEdge(getEdge(b).nextE).st == getEdge(b).en) + { + _aretes[getEdge(b).nextE].prevS = a; + } + else if (getEdge(getEdge(b).nextE).en == getEdge(b).en) + { + _aretes[getEdge(b).nextE].prevE = a; + } + } + + + for (int i = 0; i < 2; i++) { + int p = getEdge(b).st; + if (p >= 0) { + if (getPoint(p).incidentEdge[i] == b) { + _pts[p].incidentEdge[i] = a; + } + } + + p = getEdge(b).en; + if (p >= 0) { + if (getPoint(p).incidentEdge[i] == b) { + _pts[p].incidentEdge[i] = a; + } + } + + p = getEdge(a).st; + if (p >= 0) { + if (getPoint(p).incidentEdge[i] == numberOfEdges()) { + _pts[p].incidentEdge[i] = b; + } + } + + p = getEdge(a).en; + if (p >= 0) { + if (getPoint(p).incidentEdge[i] == numberOfEdges()) { + _pts[p].incidentEdge[i] = b; + } + } + + } + + if (getEdge(a).prevS == b) + _aretes[a].prevS = a; + if (getEdge(a).prevE == b) + _aretes[a].prevE = a; + if (getEdge(a).nextS == b) + _aretes[a].nextS = a; + if (getEdge(a).nextE == b) + _aretes[a].nextE = a; + if (getEdge(b).prevS == a) + _aretes[a].prevS = b; + if (getEdge(b).prevE == a) + _aretes[a].prevE = b; + if (getEdge(b).nextS == a) + _aretes[a].nextS = b; + if (getEdge(b).nextE == a) + _aretes[a].nextE = b; + + dg_arete swap = getEdge(a); + _aretes[a] = getEdge(b); + _aretes[b] = swap; + if (_has_edges_data) + { + edge_data swae = eData[a]; + eData[a] = eData[b]; + eData[b] = swae; + } + if (_has_sweep_src_data) + { + sweep_src_data swae = swsData[a]; + swsData[a] = swsData[b]; + swsData[b] = swae; + } + if (_has_sweep_dest_data) + { + sweep_dest_data swae = swdData[a]; + swdData[a] = swdData[b]; + swdData[b] = swae; + } + if (_has_raster_data) + { + raster_data swae = swrData[a]; + swrData[a] = swrData[b]; + swrData[b] = swae; + } + if (_has_back_data) + { + back_data swae = ebData[a]; + ebData[a] = ebData[b]; + ebData[b] = swae; + } + if (_has_voronoi_data) + { + voronoi_edge swav = voreData[a]; + voreData[a] = voreData[b]; + voreData[b] = swav; + } +} +void +Shape::SwapEdges (int a, int b, int c) +{ + if (a == b || b == c || a == c) + return; + SwapEdges (a, b); + SwapEdges (b, c); +} + +void +Shape::SortEdges () +{ + if (_need_edges_sorting == false) { + return; + } + _need_edges_sorting = false; + + // allocate the edge_list array as it's needed by SortEdgesList + edge_list *list = (edge_list *) g_malloc(numberOfEdges() * sizeof (edge_list)); + // for each point + for (int p = 0; p < numberOfPoints(); p++) + { + int const d = getPoint(p).totalDegree(); + // if degree is greater than 1 + if (d > 1) + { + int cb; + // get the first edge + cb = getPoint(p).incidentEdge[FIRST]; + int nb = 0; + // for all the edges connected at this point, form the list + while (cb >= 0) + { + int n = nb++; + list[n].no = cb; + if (getEdge(cb).st == p) + { + list[n].x = getEdge(cb).dx; + list[n].starting = true; + } + else + { + list[n].x = -getEdge(cb).dx; + list[n].starting = false; + } + cb = NextAt (p, cb); + } + // sort the list + SortEdgesList (list, 0, nb - 1); + // recreate the linked list with the sorted edges + _pts[p].incidentEdge[FIRST] = list[0].no; + _pts[p].incidentEdge[LAST] = list[nb - 1].no; + for (int i = 0; i < nb; i++) + { + if (list[i].starting) + { + if (i > 0) + { + _aretes[list[i].no].prevS = list[i - 1].no; + } + else + { + _aretes[list[i].no].prevS = -1; + } + if (i < nb - 1) + { + _aretes[list[i].no].nextS = list[i + 1].no; + } + else + { + _aretes[list[i].no].nextS = -1; + } + } + else + { + if (i > 0) + { + _aretes[list[i].no].prevE = list[i - 1].no; + } + else + { + _aretes[list[i].no].prevE = -1; + } + if (i < nb - 1) + { + _aretes[list[i].no].nextE = list[i + 1].no; + } + else + { + _aretes[list[i].no].nextE = -1; + } + } + } + } + } + g_free(list); +} + +int +Shape::CmpToVert (Geom::Point ax, Geom::Point bx,bool as,bool bs) +{ + // these tst variables store the sign of the x and y coordinates of each of the vectors + // + is +1, - is -1 and 0 is 0 + int tstAX = 0; + int tstAY = 0; + int tstBX = 0; + int tstBY = 0; + if (ax[0] > 0) + tstAX = 1; + if (ax[0] < 0) + tstAX = -1; + if (ax[1] > 0) + tstAY = 1; + if (ax[1] < 0) + tstAY = -1; + if (bx[0] > 0) + tstBX = 1; + if (bx[0] < 0) + tstBX = -1; + if (bx[1] > 0) + tstBY = 1; + if (bx[1] < 0) + tstBY = -1; + + // calcuate quadrant for both a and b edge vectors + // for quadrant numbers, see the figure in header file documentation of this + // function + int quadA = 0, quadB = 0; + if (tstAX < 0) + { + if (tstAY < 0) + { + quadA = 7; + } + else if (tstAY == 0) + { + quadA = 6; + } + else if (tstAY > 0) + { + quadA = 5; + } + } + else if (tstAX == 0) + { + if (tstAY < 0) + { + quadA = 0; + } + else if (tstAY == 0) + { + quadA = -1; + } + else if (tstAY > 0) + { + quadA = 4; + } + } + else if (tstAX > 0) + { + if (tstAY < 0) + { + quadA = 1; + } + else if (tstAY == 0) + { + quadA = 2; + } + else if (tstAY > 0) + { + quadA = 3; + } + } + if (tstBX < 0) + { + if (tstBY < 0) + { + quadB = 7; + } + else if (tstBY == 0) + { + quadB = 6; + } + else if (tstBY > 0) + { + quadB = 5; + } + } + else if (tstBX == 0) + { + if (tstBY < 0) + { + quadB = 0; + } + else if (tstBY == 0) + { + quadB = -1; + } + else if (tstBY > 0) + { + quadB = 4; + } + } + else if (tstBX > 0) + { + if (tstBY < 0) + { + quadB = 1; + } + else if (tstBY == 0) + { + quadB = 2; + } + else if (tstBY > 0) + { + quadB = 3; + } + } + + + // B should have a smaller quadrant number, if the opposite is true, we need to swap + if (quadA < quadB) + return 1; + if (quadA > quadB) + return -1; + + // if the quadrants are same, we need to do more maths to figure out their relative orientation + + Geom::Point av, bv; + av = ax; + bv = bx; + + // take cross from av to bv + // cross product in SVG coordinates is different visually. With your right hand, let index finger point to vector + // av, and let middle finger point to vector bv, if thumb is out of page, cross is negative, if into the page, it's + // positive + double si = cross(av, bv); + // if the angle that bv makes from -y axis is smaller than the one av makes, our arrangement is good and needs no swap + // this ideal orientation will give a negative cross product (thumb out of page) + int tstSi = 0; + if (si > 0.000001) tstSi = 1; // if positive cross product, swap needed + if (si < -0.000001) tstSi = -1; // if negative cross product, we are good + + // if the edges are kinda on top of each other + // I have no idea if there is a reason behind these two rules below + if ( tstSi == 0 ) { + if ( as && !bs ) return -1; // if b ends at point and a starts, we are good + if ( !as && bs ) return 1; // if b starts at point and a ends, swap + } + + // if we stil can't decide whether a swap is needed or not (both edges on top of each other and both starting + // from the point) or ending at the point, we return 0 + return tstSi; +} + +void +Shape::SortEdgesList (edge_list * list, int s, int e) +{ + if (s >= e) + return; + if (e == s + 1) { + int cmpval=CmpToVert (list[e].x, list[s].x,list[e].starting,list[s].starting); + if ( cmpval > 0 ) { // priorite aux sortants + edge_list swap = list[s]; + list[s] = list[e]; + list[e] = swap; + } + return; + } + + int ppos = (s + e) / 2; + int plast = ppos; + Geom::Point pvalx = list[ppos].x; + bool pvals = list[ppos].starting; + + int le = s, ri = e; + while (le < ppos || ri > plast) + { + if (le < ppos) + { + do + { + int test = CmpToVert (pvalx, list[le].x,pvals,list[le].starting); + if (test == 0) + { + // on colle les valeurs egales au pivot ensemble + if (le < ppos - 1) + { + edge_list swap = list[le]; + list[le] = list[ppos - 1]; + list[ppos - 1] = list[ppos]; + list[ppos] = swap; + ppos--; + continue; // sans changer le + } + else if (le == ppos - 1) + { + ppos--; + break; + } + else + { + // oupsie + break; + } + } + if (test > 0) + { + break; + } + le++; + } + while (le < ppos); + } + if (ri > plast) + { + do + { + int test = CmpToVert (pvalx, list[ri].x,pvals,list[ri].starting); + if (test == 0) + { + // on colle les valeurs egales au pivot ensemble + if (ri > plast + 1) + { + edge_list swap = list[ri]; + list[ri] = list[plast + 1]; + list[plast + 1] = list[plast]; + list[plast] = swap; + plast++; + continue; // sans changer ri + } + else if (ri == plast + 1) + { + plast++; + break; + } + else + { + // oupsie + break; + } + } + if (test < 0) + { + break; + } + ri--; + } + while (ri > plast); + } + + if (le < ppos) + { + if (ri > plast) + { + edge_list swap = list[le]; + list[le] = list[ri]; + list[ri] = swap; + le++; + ri--; + } + else if (le < ppos - 1) + { + edge_list swap = list[ppos - 1]; + list[ppos - 1] = list[plast]; + list[plast] = list[le]; + list[le] = swap; + ppos--; + plast--; + } + else if (le == ppos - 1) + { + edge_list swap = list[plast]; + list[plast] = list[le]; + list[le] = swap; + ppos--; + plast--; + } + else + { + break; + } + } + else + { + if (ri > plast + 1) + { + edge_list swap = list[plast + 1]; + list[plast + 1] = list[ppos]; + list[ppos] = list[ri]; + list[ri] = swap; + ppos++; + plast++; + } + else if (ri == plast + 1) + { + edge_list swap = list[ppos]; + list[ppos] = list[ri]; + list[ri] = swap; + ppos++; + plast++; + } + else + { + break; + } + } + } + SortEdgesList (list, s, ppos - 1); + SortEdgesList (list, plast + 1, e); + +} + + + +/* + * + */ +void +Shape::ConnectStart (int p, int b) +{ + if (getEdge(b).st >= 0) + DisconnectStart (b); + + _aretes[b].st = p; + _pts[p].dO++; + _aretes[b].nextS = -1; + _aretes[b].prevS = getPoint(p).incidentEdge[LAST]; + if (getPoint(p).incidentEdge[LAST] >= 0) + { + if (getEdge(getPoint(p).incidentEdge[LAST]).st == p) + { + _aretes[getPoint(p).incidentEdge[LAST]].nextS = b; + } + else if (getEdge(getPoint(p).incidentEdge[LAST]).en == p) + { + _aretes[getPoint(p).incidentEdge[LAST]].nextE = b; + } + } + _pts[p].incidentEdge[LAST] = b; + if (getPoint(p).incidentEdge[FIRST] < 0) + _pts[p].incidentEdge[FIRST] = b; +} + +void +Shape::ConnectEnd (int p, int b) +{ + if (getEdge(b).en >= 0) + DisconnectEnd (b); + _aretes[b].en = p; + _pts[p].dI++; + _aretes[b].nextE = -1; + _aretes[b].prevE = getPoint(p).incidentEdge[LAST]; + if (getPoint(p).incidentEdge[LAST] >= 0) + { + if (getEdge(getPoint(p).incidentEdge[LAST]).st == p) + { + _aretes[getPoint(p).incidentEdge[LAST]].nextS = b; + } + else if (getEdge(getPoint(p).incidentEdge[LAST]).en == p) + { + _aretes[getPoint(p).incidentEdge[LAST]].nextE = b; + } + } + _pts[p].incidentEdge[LAST] = b; + if (getPoint(p).incidentEdge[FIRST] < 0) + _pts[p].incidentEdge[FIRST] = b; +} + +void +Shape::DisconnectStart (int b) +{ + if (getEdge(b).st < 0) + return; + _pts[getEdge(b).st].dO--; + if (getEdge(b).prevS >= 0) + { + if (getEdge(getEdge(b).prevS).st == getEdge(b).st) + { + _aretes[getEdge(b).prevS].nextS = getEdge(b).nextS; + } + else if (getEdge(getEdge(b).prevS).en == getEdge(b).st) + { + _aretes[getEdge(b).prevS].nextE = getEdge(b).nextS; + } + } + if (getEdge(b).nextS >= 0) + { + if (getEdge(getEdge(b).nextS).st == getEdge(b).st) + { + _aretes[getEdge(b).nextS].prevS = getEdge(b).prevS; + } + else if (getEdge(getEdge(b).nextS).en == getEdge(b).st) + { + _aretes[getEdge(b).nextS].prevE = getEdge(b).prevS; + } + } + if (getPoint(getEdge(b).st).incidentEdge[FIRST] == b) + _pts[getEdge(b).st].incidentEdge[FIRST] = getEdge(b).nextS; + if (getPoint(getEdge(b).st).incidentEdge[LAST] == b) + _pts[getEdge(b).st].incidentEdge[LAST] = getEdge(b).prevS; + _aretes[b].st = -1; +} + +void +Shape::DisconnectEnd (int b) +{ + if (getEdge(b).en < 0) + return; + _pts[getEdge(b).en].dI--; + if (getEdge(b).prevE >= 0) + { + if (getEdge(getEdge(b).prevE).st == getEdge(b).en) + { + _aretes[getEdge(b).prevE].nextS = getEdge(b).nextE; + } + else if (getEdge(getEdge(b).prevE).en == getEdge(b).en) + { + _aretes[getEdge(b).prevE].nextE = getEdge(b).nextE; + } + } + if (getEdge(b).nextE >= 0) + { + if (getEdge(getEdge(b).nextE).st == getEdge(b).en) + { + _aretes[getEdge(b).nextE].prevS = getEdge(b).prevE; + } + else if (getEdge(getEdge(b).nextE).en == getEdge(b).en) + { + _aretes[getEdge(b).nextE].prevE = getEdge(b).prevE; + } + } + if (getPoint(getEdge(b).en).incidentEdge[FIRST] == b) + _pts[getEdge(b).en].incidentEdge[FIRST] = getEdge(b).nextE; + if (getPoint(getEdge(b).en).incidentEdge[LAST] == b) + _pts[getEdge(b).en].incidentEdge[LAST] = getEdge(b).prevE; + _aretes[b].en = -1; +} + + +void +Shape::Inverse (int b) +{ + int swap; + swap = getEdge(b).st; + _aretes[b].st = getEdge(b).en; + _aretes[b].en = swap; + swap = getEdge(b).prevE; + _aretes[b].prevE = getEdge(b).prevS; + _aretes[b].prevS = swap; + swap = getEdge(b).nextE; + _aretes[b].nextE = getEdge(b).nextS; + _aretes[b].nextS = swap; + _aretes[b].dx = -getEdge(b).dx; + if (getEdge(b).st >= 0) + { + _pts[getEdge(b).st].dO++; + _pts[getEdge(b).st].dI--; + } + if (getEdge(b).en >= 0) + { + _pts[getEdge(b).en].dO--; + _pts[getEdge(b).en].dI++; + } + if (_has_edges_data) + eData[b].weight = -eData[b].weight; + if (_has_sweep_dest_data) + { + int swap = swdData[b].leW; + swdData[b].leW = swdData[b].riW; + swdData[b].riW = swap; + } + if (_has_back_data) + { + double swat = ebData[b].tSt; + ebData[b].tSt = ebData[b].tEn; + ebData[b].tEn = swat; + } + if (_has_voronoi_data) + { + int swai = voreData[b].leF; + voreData[b].leF = voreData[b].riF; + voreData[b].riF = swai; + } +} +void +Shape::CalcBBox (bool strict_degree) +{ + if (_bbox_up_to_date) + return; + if (hasPoints() == false) + { + leftX = rightX = topY = bottomY = 0; + _bbox_up_to_date = true; + return; + } + leftX = rightX = getPoint(0).x[0]; + topY = bottomY = getPoint(0).x[1]; + bool not_set=true; + for (int i = 0; i < numberOfPoints(); i++) + { + if ( strict_degree == false || getPoint(i).dI > 0 || getPoint(i).dO > 0 ) { + if ( not_set ) { + leftX = rightX = getPoint(i).x[0]; + topY = bottomY = getPoint(i).x[1]; + not_set=false; + } else { + if ( getPoint(i).x[0] < leftX) leftX = getPoint(i).x[0]; + if ( getPoint(i).x[0] > rightX) rightX = getPoint(i).x[0]; + if ( getPoint(i).x[1] < topY) topY = getPoint(i).x[1]; + if ( getPoint(i).x[1] > bottomY) bottomY = getPoint(i).x[1]; + } + } + } + + _bbox_up_to_date = true; +} + +// winding of a point with respect to the Shape +// 0= outside +// 1= inside (or -1, that usually the same) +// other=depends on your fill rule +// if the polygon is uncrossed, it's all the same, usually +int +Shape::PtWinding (const Geom::Point px) const +{ + int lr = 0, ll = 0, rr = 0; + + for (int i = 0; i < numberOfEdges(); i++) + { + Geom::Point const adir = getEdge(i).dx; + + Geom::Point const ast = getPoint(getEdge(i).st).x; + Geom::Point const aen = getPoint(getEdge(i).en).x; + + //int const nWeight = eData[i].weight; + int const nWeight = 1; + + if (ast[0] < aen[0]) { + if (ast[0] > px[0]) continue; + if (aen[0] < px[0]) continue; + } else { + if (ast[0] < px[0]) continue; + if (aen[0] > px[0]) continue; + } + if (ast[0] == px[0]) { + if (ast[1] >= px[1]) continue; + if (aen[0] == px[0]) continue; + if (aen[0] < px[0]) ll += nWeight; else rr -= nWeight; + continue; + } + if (aen[0] == px[0]) { + if (aen[1] >= px[1]) continue; + if (ast[0] == px[0]) continue; + if (ast[0] < px[0]) ll -= nWeight; else rr += nWeight; + continue; + } + + if (ast[1] < aen[1]) { + if (ast[1] >= px[1]) continue; + } else { + if (aen[1] >= px[1]) continue; + } + + Geom::Point const diff = px - ast; + double const cote = cross(adir, diff); + if (cote == 0) continue; + if (cote < 0) { + if (ast[0] > px[0]) lr += nWeight; + } else { + if (ast[0] < px[0]) lr -= nWeight; + } + } + return lr + (ll + rr) / 2; +} + + +void Shape::initialisePointData() +{ + if (_point_data_initialised) + return; + int const N = numberOfPoints(); + + for (int i = 0; i < N; i++) { + pData[i].pending = 0; + pData[i].edgeOnLeft = -1; + pData[i].nextLinkedPoint = -1; + pData[i].rx[0] = Round(getPoint(i).x[0]); + pData[i].rx[1] = Round(getPoint(i).x[1]); + } + + _point_data_initialised = true; +} + +void Shape::initialiseEdgeData() +{ + int const N = numberOfEdges(); + + for (int i = 0; i < N; i++) { + eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx; + eData[i].length = dot(eData[i].rdx, eData[i].rdx); + eData[i].ilength = 1 / eData[i].length; + eData[i].sqlength = sqrt(eData[i].length); + eData[i].isqlength = 1 / eData[i].sqlength; + eData[i].siEd = eData[i].rdx[1] * eData[i].isqlength; + eData[i].coEd = eData[i].rdx[0] * eData[i].isqlength; + + if (eData[i].siEd < 0) { + eData[i].siEd = -eData[i].siEd; + eData[i].coEd = -eData[i].coEd; + } + + swsData[i].misc = nullptr; + swsData[i].firstLinkedPoint = -1; + swsData[i].stPt = swsData[i].enPt = -1; + swsData[i].leftRnd = swsData[i].rightRnd = -1; + swsData[i].nextSh = nullptr; + swsData[i].nextBo = -1; + swsData[i].curPoint = -1; + swsData[i].doneTo = -1; + } +} + + +void Shape::clearIncidenceData() +{ + g_free(iData); + iData = nullptr; + nbInc = maxInc = 0; +} + + + +/** + * A directed graph is Eulerian iff every vertex has equal indegree and outdegree. + * http://mathworld.wolfram.com/EulerianGraph.html + * + * \param s Directed shape. + * \return true if s is Eulerian. + */ + +bool directedEulerian(Shape const *s) +{ + for (int i = 0; i < s->numberOfPoints(); i++) { + if (s->getPoint(i).dI != s->getPoint(i).dO) { + return false; + } + } + + return true; +} + + + +/** + * \param s Shape. + * \param p Point. + * \return Minimum distance from p to any of the points or edges of s. + */ + +double distance(Shape const *s, Geom::Point const &p) +{ + if ( s->hasPoints() == false) { + return 0.0; + } + + /* Find the minimum distance from p to one of the points on s. + ** Computing the dot product of the difference vector gives + ** us the distance squared; we can leave the square root + ** until the end. + */ + double bdot = Geom::dot(p - s->getPoint(0).x, p - s->getPoint(0).x); + + for (int i = 0; i < s->numberOfPoints(); i++) { + Geom::Point const offset( p - s->getPoint(i).x ); + double ndot = Geom::dot(offset, offset); + if ( ndot < bdot ) { + bdot = ndot; + } + } + + for (int i = 0; i < s->numberOfEdges(); i++) { + if ( s->getEdge(i).st >= 0 && s->getEdge(i).en >= 0 ) { + /* The edge has start and end points */ + Geom::Point const st(s->getPoint(s->getEdge(i).st).x); // edge start + Geom::Point const en(s->getPoint(s->getEdge(i).en).x); // edge end + + Geom::Point const d(p - st); // vector between p and edge start + Geom::Point const e(en - st); // vector of the edge + double const el = Geom::dot(e, e); // edge length + + /* Update bdot if appropriate */ + if ( el > 0.001 ) { + double const npr = Geom::dot(d, e); + if ( npr > 0 && npr < el ) { + double const nl = fabs( Geom::cross(d, e) ); + double ndot = nl * nl / el; + if ( ndot < bdot ) { + bdot = ndot; + } + } + } + } + } + + return sqrt(bdot); +} + + + +/** + * Returns true iff the L2 distance from \a thePt to this shape is <= \a max_l2. + * Distance = the min of distance to its points and distance to its edges. + * Points without edges are considered, which is maybe unwanted... + * + * This is largely similar to distance(). + * + * \param s Shape. + * \param p Point. + * \param max_l2 L2 distance. + */ + +bool distanceLessThanOrEqual(Shape const *s, Geom::Point const &p, double const max_l2) +{ + if ( s->hasPoints() == false ) { + return false; + } + + /* TODO: Consider using bbox to return early, perhaps conditional on nbPt or nbAr. */ + + /* TODO: Efficiency: In one test case (scribbling with the freehand tool to create a small number of long + ** path elements), changing from a Distance method to a DistanceLE method reduced this + ** function's CPU time from about 21% of total inkscape CPU time to 14-15% of total inkscape + ** CPU time, due to allowing early termination. I don't know how much the L1 test helps, it + ** may well be a case of premature optimization. Consider testing dot(offset, offset) + ** instead. + */ + + double const max_l1 = max_l2 * M_SQRT2; + for (int i = 0; i < s->numberOfPoints(); i++) { + Geom::Point const offset( p - s->getPoint(i).x ); + double const l1 = Geom::L1(offset); + if ( (l1 <= max_l2) || ((l1 <= max_l1) && (Geom::L2(offset) <= max_l2)) ) { + return true; + } + } + + for (int i = 0; i < s->numberOfEdges(); i++) { + if ( s->getEdge(i).st >= 0 && s->getEdge(i).en >= 0 ) { + Geom::Point const st(s->getPoint(s->getEdge(i).st).x); + Geom::Point const en(s->getPoint(s->getEdge(i).en).x); + Geom::Point const d(p - st); + Geom::Point const e(en - st); + double const el = Geom::L2(e); + if ( el > 0.001 ) { + Geom::Point const e_unit(e / el); + double const npr = Geom::dot(d, e_unit); + if ( npr > 0 && npr < el ) { + double const nl = fabs(Geom::cross(d, e_unit)); + if ( nl <= max_l2 ) { + return true; + } + } + } + } + } + + return false; +} + +//}; + diff --git a/src/livarot/Shape.h b/src/livarot/Shape.h new file mode 100644 index 0000000..bc2a547 --- /dev/null +++ b/src/livarot/Shape.h @@ -0,0 +1,1202 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef my_shape +#define my_shape + +#include <cmath> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <vector> +#include <2geom/point.h> + +#include "livarot/LivarotDefs.h" +#include "object/object-set.h" // For BooleanOp + +class Path; +class FloatLigne; + +class SweepTree; +class SweepTreeList; +class SweepEventQueue; + +enum { + tweak_mode_grow, + tweak_mode_push, + tweak_mode_repel, + tweak_mode_roughen +}; + +/* + * the Shape class (was the Digraph class, as the header says) stores digraphs (no kidding!) of which + * a very interesting kind are polygons. + * the main use of this class is the ConvertToShape() (or Booleen(), quite the same) function, which + * removes all problems a polygon can present: duplicate points or edges, self-intersection. you end up with a + * full-fledged polygon + */ + +// possible values for the "type" field in the Shape class: +enum +{ + shape_graph = 0, // it's just a graph; a bunch of edges, maybe intersections + shape_polygon = 1, // a polygon: intersection-free, edges oriented so that the inside is on their left + shape_polypatch = 2 // a graph without intersection; each face is a polygon (not yet used) +}; + +class BitLigne; +class AlphaLigne; + +/** + * A class to store/manipulate directed graphs. + * + * This class is at the heart of everything we do in Livarot. When you first populate a Shape by calling + * Path::Fill, it makes a directed graph of the type shape_graph. This one is exactly identical to the original + * polyline except that it's a graph. Later, you call Shape::ConvertToShape to create another directed graph + * from this one that is totally intersection free. All the intersections would have been calculated, edges broken + * up at those points, all edges flipped such that inside is to their left. You ofcourse need a fill rule to do this. + * + * Once that's done, you can do all interesting operations such as Tweaking, Offsetting and Boolean Operations. + */ +class Shape +{ +public: + + /** + * A structure to store back data for an edge. + */ + struct back_data + { + int pathID; /*!< This is a unique number unique to a Path object given by the user to the Path::Fill function. */ + int pieceID; /*!< The path command to which this edge belongs to in the original Path object. */ + double tSt; /*!< Time value in that path command for this edge's start point. */ + double tEn; /*!< Time value in that path command for this edge's end point. */ + }; + + struct voronoi_point + { // info for points treated as points of a voronoi diagram (obtained by MakeShape()) + double value; // distance to source + int winding; // winding relatively to source + }; + + struct voronoi_edge + { // info for edges, treated as approximation of edges of the voronoi diagram + int leF, riF; // left and right site + double leStX, leStY, riStX, riStY; // on the left side: (leStX,leStY) is the smallest vector from the source to st + // etc... + double leEnX, leEnY, riEnX, riEnY; + }; + + struct quick_raster_data + { + double x; // x-position on the sweepline + int bord; // index of the edge + int ind; // index of qrsData elem for edge (ie inverse of the bord) + int next,prev; // dbl linkage + }; + + /** + * Enum describing all the events that can happen to a sweepline. + */ + enum sTreeChangeType + { + EDGE_INSERTED = 0, /*!< A new edge got added. */ + EDGE_REMOVED = 1, /*!< An edge got removed. */ + INTERSECTION = 2 /*!< An intersection got detected. */ + }; + + /** + * A structure that represents a change that took place in the sweepline. + */ + struct sTreeChange + { + sTreeChangeType type; // type of modification to the sweepline: + int ptNo; // point at which the modification takes place + + Shape *src; // left edge (or unique edge if not an intersection) involved in the event + int bord; + Shape *osrc; // right edge (if intersection) + int obord; + Shape *lSrc; // edge directly on the left in the sweepline at the moment of the event + int lBrd; + Shape *rSrc; // edge directly on the right + int rBrd; + }; + + struct incidenceData + { + int nextInc; // next incidence in the linked list + int pt; // point incident to the edge (there is one list per edge) + double theta; // coordinate of the incidence on the edge + }; + + Shape(); + virtual ~Shape(); + + void MakeBackData(bool nVal); + void MakeVoronoiData(bool nVal); + + void Affiche(); + + // insertion/deletion/movement of elements in the graph + void Copy(Shape *a); + // -reset the graph, and ensure there's room for n points and m edges + + /** + * Clear all data. + * + * Set shape type to shape_polygon. Clear all points and edges. Make room for + * enough points and edges. + * + * @param n Number of points to make space for. + * @param m Number of edges to make space for. + */ + void Reset(int n = 0, int m = 0); + // -points: + int AddPoint(const Geom::Point x); // as the function name says + // returns the index at which the point has been added in the array + void SubPoint(int p); // removes the point at index p + // nota: this function relocates the last point to the index p + // so don't trust point indices if you use SubPoint + void SwapPoints(int a, int b); // swaps 2 points at indices a and b + void SwapPoints(int a, int b, int c); // swaps 3 points: c <- a <- b <- c + + + /** + * Sort the points (all points) only if needed. (checked by flag) + * + * See the index version of SortPoints since this is exactly the same. + */ + void SortPoints(); // sorts the points if needed (checks the need_points_sorting flag) + + // -edges: + // add an edge between points of indices st and en + int AddEdge(int st, int en); + // return the edge index in the array + + // add an edge between points of indices st and en + int AddEdge(int st, int en, int leF, int riF); + // return the edge index in the array + + // version for the voronoi (with faces IDs) + void SubEdge(int e); // removes the edge at index e (same remarks as for SubPoint) + void SwapEdges(int a, int b); // swaps 2 edges + void SwapEdges(int a, int b, int c); // swaps 3 edges + + /** + * Sort all edges (clockwise) around each point. + * + * The function operates on each point and ensures that the linked list of the edges + * connected to a point is in the clockwise direction spatially. The clockwise + * angle that an edge line segment makes with the -y axis should increase (or remain same) as we move + * forward in the linked list of edges. + * + * This sorting is done using edge vectors however note that if an edge ends at a point instead of starting + * from there, we invert the edge to make the edge vector look like it started from there. + */ + void SortEdges(); // sort the edges if needed (checks the need_edges_sorting falg) + + // primitives for topological manipulations + + // endpoint of edge at index b that is different from the point p + inline int Other(int p, int b) const + { + if (getEdge(b).st == p) { + return getEdge(b).en; + } + return getEdge(b).st; + } + + // next edge (after edge b) in the double-linked list at point p + inline int NextAt(int p, int b) const + { + if (p == getEdge(b).st) { + return getEdge(b).nextS; + } + else if (p == getEdge(b).en) { + return getEdge(b).nextE; + } + + return -1; + } + + // previous edge + inline int PrevAt(int p, int b) const + { + if (p == getEdge(b).st) { + return getEdge(b).prevS; + } + else if (p == getEdge(b).en) { + return getEdge(b).prevE; + } + + return -1; + } + + // same as NextAt, but the list is considered circular + inline int CycleNextAt(int p, int b) const + { + if (p == getEdge(b).st) { + if (getEdge(b).nextS < 0) { + return getPoint(p).incidentEdge[FIRST]; + } + return getEdge(b).nextS; + } else if (p == getEdge(b).en) { + if (getEdge(b).nextE < 0) { + return getPoint(p).incidentEdge[FIRST]; + } + + return getEdge(b).nextE; + } + + return -1; + } + + // same as PrevAt, but the list is considered circular + inline int CyclePrevAt(int p, int b) const + { + if (p == getEdge(b).st) { + if (getEdge(b).prevS < 0) { + return getPoint(p).incidentEdge[LAST]; + } + return getEdge(b).prevS; + } else if (p == getEdge(b).en) { + if (getEdge(b).prevE < 0) { + return getPoint(p).incidentEdge[LAST]; + } + return getEdge(b).prevE; + } + + return -1; + } + + void ConnectStart(int p, int b); // set the point p as the start of edge b + void ConnectEnd(int p, int b); // set the point p as the end of edge b + void DisconnectStart(int b); // disconnect edge b from its start point + void DisconnectEnd(int b); // disconnect edge b from its end point + + // reverses edge b (start <-> end) + void Inverse(int b); + // calc bounding box and sets leftX,rightX,topY and bottomY to their values + void CalcBBox(bool strict_degree = false); + + // debug function: plots the graph (mac only) + void Plot(double ix, double iy, double ir, double mx, double my, bool doPoint, + bool edgesNo, bool pointNo, bool doDir, char *fileName); + + // transforms a polygon in a "forme" structure, ie a set of contours, which can be holes (see ShapeUtils.h) + // return NULL in case it's not possible + + /** + * Extract contours from a directed graph. + * + * The function doesn't care about any back data and thus all contours will be made up + * of line segments. Any original curves would be lost. + * + * The traversal algorithm is totally identical to GetWindings with minor differences. + * + * @param[out] dest Pointer to the path where the extracted contours will be stored. + */ + void ConvertToForme(Path *dest); + + // version to use when conversion was done with ConvertWithBackData(): will attempt to merge segment belonging to + // the same curve + // nota: apparently the function doesn't like very small segments of arc + + /** + * Extract contours from a directed graph while using back data. + * + * Since back data is used, the original curves are preserved. + * + * @param[out] dest Pointer to the path where the extracted contours will be stored. + * @param nbP Number of paths that were originally fed to the directed graph with Path::Fill. + * @param orig An array of pointers to Path, one Path object for each path id in the graph. + * @param splitWhenForced TODO: Figure this out. + */ + void ConvertToForme(Path *dest, int nbP, Path **orig, bool splitWhenForced = false); + // version trying to recover the nesting of subpaths (ie: holes) + void ConvertToFormeNested(Path *dest, int nbP, Path **orig, int wildPath, int &nbNest, + int *&nesting, int *&contStart, bool splitWhenForced = false); + + // sweeping a digraph to produce a intersection-free polygon + // return 0 if everything is ok and a return code otherwise (see LivarotDefs.h) + // the input is the Shape "a" + // directed=true <=> non-zero fill rule + + /** + * Using a given fill rule, find all intersections in the shape given, create a new + * intersection free shape in the instance. + * + * The word "instance" or "this" is used throughout the documentation to refer to the instance on which + * the function was called. + * + * Details of the algorithm: + * The function does four things more or less: + * 1. Find all self-intersections in the shape. + * 2. Reconstruct the directed graph with the intersections now converted to vertices. + * 3. Compute winding number seeds for later use by GetWindings. + * 4. Do some processing on edges by calling AssembleAretes. + * 5. Compute winding numbers and accordingly manipulate edges. (Deciding whether to keep, invert or destroy them) + * + * Finding self-intersections and reconstruction happens simultaneously. The function has a huge loop that moves + * a sweepline top to bottom, finding intersections and reconstructing the new directed graph. Edges are added/removed + * in the sweepline tree sTree and intersections detected go in sEvts. All events (Edge Addition/Removal/Intersection) + * that take place at a constant `y` value are recorded in an array named `chgts` and the function call to CheckEdges + * does the reconstruction. + * + * One important thing to note is that usually in a Bentley-Ottman sweepline algorithm, the heap also contains endpoints + * (where edges start or end) but in this implementation that's not the case. The main loop takes care of the endpoints + * and the heap takes care of intersections only. + * + * If you want a good theoretical overview of how all these things are done, please see the docs in livarot-doxygen.cpp. + * + * @param a The pointer to the shape that we want to process. + * @param directed The fill rule. + * @param invert TODO: Be sure about what this does + * + * @return 0 if everything went good, error code otherwise. (see LivarotDefs.h) + */ + int ConvertToShape(Shape *a, FillRule directed = fill_nonZero, bool invert = false); + + + // directed=false <=> even-odd fill rule + // invert=true: make as if you inverted all edges in the source + int Reoriente(Shape *a); // subcase of ConvertToShape: the input a is already intersection-free + // all that's missing are the correct directions of the edges + // Reoriented is equivalent to ConvertToShape(a,false,false) , but faster sicne + // it doesn't computes interections nor adjacencies + void ForceToPolygon(); // force the Shape to believe it's a polygon (eulerian+intersection-free+no + // duplicate edges+no duplicate points) + // be careful when using this function + + // the coordinate rounding function + inline static double Round(double x) + { + return ldexp(rint(ldexp(x, 9)), -9); + } + + // 2 miscannellous variations on it, to scale to and back the rounding grid + inline static double HalfRound(double x) + { + return ldexp(x, -9); + } + + inline static double IHalfRound(double x) + { + return ldexp(x, 9); + } + + // boolean operations on polygons (requests intersection-free poylygons) + // boolean operation types are defined in LivarotDefs.h + // same return code as ConvertToShape + int Booleen(Shape *a, Shape *b, BooleanOp mod, int cutPathID = -1); + + // create a graph that is an offseted version of the graph "of" + // the offset is dec, with joins between edges of type "join" (see LivarotDefs.h) + // the result is NOT a polygon; you need a subsequent call to ConvertToShape to get a real polygon + int MakeOffset(Shape *of, double dec, JoinType join, double miter, bool do_profile=false, double cx = 0, double cy = 0, double radius = 0, Geom::Affine *i2doc = nullptr); + + int MakeTweak (int mode, Shape *a, double dec, JoinType join, double miter, bool do_profile, Geom::Point c, Geom::Point vector, double radius, Geom::Affine *i2doc); + + int PtWinding(const Geom::Point px) const; // plus rapide + /** + * Compute the winding number of the point given. (brutually) + * + * The function works by bringing in a ray from (px.x, -infinity) + * to (px.x, px.y) and seeing how many edges it cuts and the direction + * of those edges. It uses this information to compute the winding number. + * + * The way this function works is that it iterates through all the edges + * and for each edge it checks if the ray will intersect the edge and in + * which orientation. See the function body to see exactly how this works. + * + * @image html livarot-images/winding-brutal-bounds.svg + * @image html livarot-images/winding-brutal-endpoints-start.svg + * @image html livarot-images/winding-brutal-endpoints-end.svg + * + * The algorithm is quite simple. For edges that simply cut the ray, we check + * the direction of the edge and accordingly add/subtract from a variable to keep + * track of the winding. However, a different case comes up when an edge has an + * endpoint that cuts the ray. You can just see the direction and maybe change the same + * variable, but then the problem is, another edge connected to the same point will also + * do the same and you'd have two additions when you should only have one. Hence, the solution + * is, we create two variables ll and rr and add/subtract to them, then, we sum them and divide + * by 2 to get the contribution to the winding number. + * + * @image html livarot-images/winding-brutal-endpoints.svg + * + * @param px The point whose winding number to compute + * + * @return The winding number of the point px. + */ + int Winding(const Geom::Point px) const; + + // rasterization + void BeginRaster(float &pos, int &curPt); + void EndRaster(); + void BeginQuickRaster(float &pos, int &curPt); + void EndQuickRaster(); + + void Scan(float &pos, int &curP, float to, float step); + void QuickScan(float &pos, int &curP, float to, bool doSort, float step); + void DirectScan(float &pos, int &curP, float to, float step); + void DirectQuickScan(float &pos, int &curP, float to, bool doSort, float step); + + void Scan(float &pos, int &curP, float to, FloatLigne *line, bool exact, float step); + void Scan(float &pos, int &curP, float to, FillRule directed, BitLigne *line, bool exact, float step); + void Scan(float &pos, int &curP, float to, AlphaLigne *line, bool exact, float step); + + void QuickScan(float &pos, int &curP, float to, FloatLigne* line, float step); + void QuickScan(float &pos, int &curP, float to, FillRule directed, BitLigne* line, float step); + void QuickScan(float &pos, int &curP, float to, AlphaLigne* line, float step); + + void Transform(Geom::Affine const &tr) + {for(auto & _pt : _pts) _pt.x*=tr;} + + std::vector<back_data> ebData; /*!< Stores the back data for each edge. */ + std::vector<voronoi_point> vorpData; + std::vector<voronoi_edge> voreData; + + int nbQRas; + int firstQRas; + int lastQRas; + quick_raster_data *qrsData; + + std::vector<sTreeChange> chgts; /*!< An array to store all the changes that happen to a sweepline within a y value */ + int nbInc; + int maxInc; + + incidenceData *iData; + // these ones are allocated at the beginning of each sweep and freed at the end of the sweep + SweepTreeList *sTree; /*!< Pointer to store the sweepline tree. To our use at least, it's a linear list of the edges that intersect with sweepline. */ + SweepEventQueue *sEvts; /*!< Intersection events that we have detected that are to come. Sorted so closest one gets popped. */ + + // bounding box stuff + double leftX, topY, rightX, bottomY; + + /** + * A point or vertex in the directed graph. + * + * Each point keeps track of the first edge that got connected to it and the last edge + * that got connected to it. By connecting we mean both an edge starting at the point or + * an edge ending at the point. This is needed for maintaining a linked list at each point. + * + * At each point, we maintain a linked list of edges that connect to that edge. incidentEdge + * keeps the first and last edge of this double-linked list. The rest of the edge pointers + * are stored in dg_arete. + */ + struct dg_point + { + Geom::Point x; /*!< The coordinates of the point. */ + int dI; /*!< Number of edges ending on this point. */ + int dO; /*!< Number of edges starting from this point. */ + int incidentEdge[2]; /*!< First (index 0) and last edge (index 1) that are attached to this point. */ + int oldDegree; /*!< TODO: Not exactly sure why this is needed. Probably somewhere the degree changes and we retain the old degree for some reason. */ + + int totalDegree() const { return dI + dO; } + }; + + /** + * An edge in the directed graph. + */ + struct dg_arete + { + Geom::Point dx; /*!< edge vector (vector from start point to end point). */ + int st; /*!< start point of the edge. */ + int en; /*!< end point of the edge. */ + int nextS; /*!< next edge in the double-linked list at the start point */ + int prevS; /*!< previous edge in the double-linked list at the start point. */ + int nextE; /*!< next edge in the double-linked list at the end point. */ + int prevE; /*!< previous edge in the double-linked list at the end point. */ + }; + + // lists of the nodes and edges + int maxPt; // [FIXME: remove this] + int maxAr; // [FIXME: remove this] + + // flags + int type; + + /** + * Returns number of points. + * + * @return Number of points. + */ + inline int numberOfPoints() const { return _pts.size(); } + + /** + * Do we have points? + * + * @return True if we do, false otherwise. + */ + inline bool hasPoints() const { return (_pts.empty() == false); } + + /** + * Returns number of edges. + * + * @return Number of edges. + */ + inline int numberOfEdges() const { return _aretes.size(); } + + /** + * Do we have edges? + * + * @return True if we do, false otherwise. + */ + inline bool hasEdges() const { return (_aretes.empty() == false); } + + /** + * Do the points need sorting? + * + * @return True if we do, false otherwise. + */ + inline void needPointsSorting() { _need_points_sorting = true; } + + /** + * Do the edges need sorting? + * + * @return True if we do, false otherwise. + */ + inline void needEdgesSorting() { _need_edges_sorting = true; } + + /** + * Do we have back data? + * + * @return True if we do, false otherwise. + */ + inline bool hasBackData() const { return _has_back_data; } + + /** + * Get a point. + * + * Be careful about the index. + * + * @param n Index of the point. + * + * @return Reference to the point. + */ + inline dg_point const &getPoint(int n) const { return _pts[n]; } + + /** + * Get an edge. + * + * Be careful about the index. + * + * @param n Index of the edge. + * + * @return Reference to the edge. + */ + inline dg_arete const &getEdge(int n) const { return _aretes[n]; } + +private: + + friend class SweepTree; + friend class SweepEvent; + friend class SweepEventQueue; + + /** + * Extra data that some algorithms use. + */ + struct edge_data + { + int weight; /*!< Weight of the edge. If weight is 2, it means there are two identical edges on top of each other. */ + Geom::Point rdx; /*!< Rounded edge vector */ + double length; /*!< length of edge vector squared. */ // <-- epic naming here folks + double sqlength; /*!< length of edge vector */ // <-- epic naming here too + double ilength; /*!< Inverse of length squared */ + double isqlength; /*!< Inverse of length */ + double siEd, coEd; /*!< siEd=abs(rdy/length) and coEd=rdx/length */ + edge_data() : weight(0), length(0.0), sqlength(0.0), ilength(0.0), isqlength(0.0), siEd(0.0), coEd(0.0) {} + // used to determine the "most horizontal" edge between 2 edges + }; + + struct sweep_src_data + { + void *misc; // pointer to the SweepTree* in the sweepline + int firstLinkedPoint; // not used + int stPt, enPt; // start- end end- points for this edge in the resulting polygon + int ind; // for the GetAdjacencies function: index in the sliceSegs array (for quick deletions) + int leftRnd, rightRnd; // leftmost and rightmost points (in the result polygon) that are incident to + // the edge, for the current sweep position + // not set if the edge doesn't start/end or intersect at the current sweep position + Shape *nextSh; // nextSh and nextBo identify the next edge in the list + int nextBo; // they are used to maintain a linked list of edge that start/end or intersect at + // the current sweep position + int curPoint, doneTo; + double curT; + }; + + struct sweep_dest_data + { + void *misc; // used to check if an edge has already been seen during the depth-first search + int suivParc, precParc; // previous and current next edge in the depth-first search + int leW, riW; // left and right winding numbers for this edge + int ind; // order of the edges during the depth-first search + }; + + struct raster_data + { + SweepTree *misc; // pointer to the associated SweepTree* in the sweepline + double lastX, lastY, curX, curY; // curX;curY is the current intersection of the edge with the sweepline + // lastX;lastY is the intersection with the previous sweepline + bool sens; // true if the edge goes down, false otherwise + double calcX; // horizontal position of the intersection of the edge with the + // previous sweepline + double dxdy, dydx; // horizontal change per unit vertical move of the intersection with the sweepline + int guess; + }; + + /** + * Extra data for points used at various ocassions. + */ + struct point_data + { + int oldInd, newInd; // back and forth indices used when sorting the points, to know where they have + // been relocated in the array + int pending; // number of intersection attached to this edge, and also used when sorting arrays + int edgeOnLeft; // not used (should help speeding up winding calculations) + int nextLinkedPoint; // not used + Shape *askForWindingS; + int askForWindingB; + Geom::Point rx; /*!< rounded coordinates of the point */ + }; + + + /** + * A structure to help with sorting edges around a point. + */ + struct edge_list + { + int no; + bool starting; + Geom::Point x; + }; + + void initialisePointData(); + void initialiseEdgeData(); + void clearIncidenceData(); + + void _countUpDown(int P, int *numberUp, int *numberDown, int *upEdge, int *downEdge) const; + void _countUpDownTotalDegree2(int P, int *numberUp, int *numberDown, int *upEdge, int *downEdge) const; + void _updateIntersection(int e, int p); + + // activation/deactivation of the temporary data arrays + + /** + * Initialize the point data cache. + * + * @param nVal If set to true, it sets some flags and then resizes pData to maxPt. Does nothing if false. + */ + void MakePointData(bool nVal); + + /** + * Initialize the edge data cache. + * + * @param nVal If set to true, it sets some flags and then resizes eData to maxAr. If set to false, it clears all edge data. + */ + void MakeEdgeData(bool nVal); + + /** + * Initialize the sweep source data cache. + * + * @param nVal If set to true, it sets some flags and then resizes swsData to maxAr. If set to false, it clears all swsData. + */ + void MakeSweepSrcData(bool nVal); + + /** + * Initialize the sweep destination data cache. + * + * @param nVal If set to true, it sets some flags and then resizes swdData to maxAr. If set to false, it clears all swdData. + */ + void MakeSweepDestData(bool nVal); + + void MakeRasterData(bool nVal); + void MakeQuickRasterData(bool nVal); + + /** + * Sort the points + * + * Nothing fancy here. Please note, sorting really means just making sure the + * points exist in the array in a sorted manner. All we do here is just change + * the point's position in the array according to their location in physical space + * and make sure all edges still point to the original point they were pointing to. (:-D) + * + * Sorting condition: Given two points LEFT and RIGHT (where LEFT's index < RIGHT's index) + * we swap only if LEFT.y > RIGHT.y || (LEFT.y == RIGHT.y && LEFT.x > RIGHT.x) + * + * After sorting, not only are the points sorted in _pts, but also in pData. So both arrays + * will have same index for the same point. + * + * Sorting algorithm looks like quick sort. + * + * @param s The start index. + * @param e The end index. + */ + void SortPoints(int s, int e); + + /** + * Sort the points (take oldInd into account) + * + * Same as SortPoints except the sorting condition. + * + * Sorting condition: Given two points LEFT and RIGHT (where LEFT's index < RIGHT's index) + * we swap only if LEFT.y > RIGHT.y || (LEFT.y == RIGHT.y && LEFT.x > RIGHT.x) || + * (LEFT.y == RIGHT.y && LEFT.x == RIGHT.x && LEFT.oldInd > RIGHT.oldInd) + * + * After sorting, not only are the points sorted in _pts, but also in pData. So both arrays + * will have same index for the same point. + * + * @param s The start index. + * @param e The end index. + */ + void SortPointsByOldInd(int s, int e); + + // fonctions annexes pour ConvertToShape et Booleen + + /** + * Prepare point data cache, edge data cache and sweep source cache. + */ + void ResetSweep(); // allocates sweep structures + + /** + * Clear point data cache, edge data cache and sweep source cache. + */ + void CleanupSweep(); // deallocates them + + // edge sorting function + + /** + * Sort edges given in a list. + * + * Swapping is done in place so the original list will be modified to a sorted one. + * + * Edges between (inclusive) edges[s] and edges[e] are all sorted. + * + * @param edges The list of edges to sort. + * @param s The index of the beginning of the list to sort. + * @param s The index of the end of the list to sort. + */ + void SortEdgesList(edge_list *edges, int s, int e); + + /** + * Test if there is an intersection of an edge on a particular side. + * + * The actual intersection checking is performed by the other TesteIntersection and this function + * calls it, creating an intersection event if an intersection is detected. + * + * @param t The pointer to the node of the edge whose intersection we wanna test. + * @param s The side that we want to test for intersection. If RIGHT, the edge on the right is tested with this one. If LEFT, the edge on the + * left is tested with this one. + * @param onlyDiff My best guess about onlyDiff is it stands for "only different". Only detect intersections if + * both edges come from different shapes, otherwise don't bother. + */ + void TesteIntersection(SweepTree *t, Side s, bool onlyDiff); // test if there is an intersection + + /** + * Test intersection between the two edges. + * + * This is the function that does the actual checking. + * + * An important point to remember is that left and right aren't just two names for + * the edges, that's how the edges should be in the sweepline at the moment, otherwise, the + * intersection won't be detected. + * + * @image html livarot-images/teste-intersection.svg + * + * This is a very special point as it prevents detecting an intersection that has already passed. See + * when an intersection has already passed, the order of nodes in the sweepline have switched, thus the + * function won't detect the intersection. + * + * @image html livarot-images/intersection-cross-product.svg + * + * @image html livarot-images/intersection-cross-products.svg + * + * @image html livarot-images/problematic-intersection-case.svg + * + * This picture is related to the intersection point calculation formulas: + * + * @image html livarot-images/intersection-point-calculation.svg + * + * \f[ |\vec{slDot}| = |\vec{left}||\vec{sl}|\sin\theta_{sl}\f] + * \f[ |\vec{elDot}| = |\vec{left}||\vec{el}|\sin\theta_{el}\f] + * + * These cross products (weirdly named) do have a direction too but you need to figure that out + * with your fingers. These here only give us the magnitude, however the actual variables in code also have + * a positive or negative sign depending on the direction. Index finger of right hand to the first vector, + * middle finger to the second vector and if thumb points out of page, cross product is negative, if it + * points in the page, cross product is positive. From figure 2 you can already guess that \f$ slDot < 0\f$ + * and \f$ elDot > 0 \f$. So let's rewrite the formula in the code while taking into account these signs. + * + * \f[ \vec{atx} = \frac{-|\vec{slDot}|*\vec{rEn} -|\vec{elDot}|*\vec{rSt}}{-|\vec{slDot}|-|\vec{elDot}|}\f] + * You can cancel out all the minus signs: + * \f[ \vec{atx} = \frac{|\vec{slDot}|*\vec{rEn} + |\vec{elDot}|*\vec{rSt}}{|\vec{slDot}|+|\vec{elDot}|}\f] + * \f[ \vec{atx} = \frac{|\vec{left}||\vec{sl}||\sin\theta_{sl}|*\vec{rEn} + |\vec{left}||\vec{el}||\sin\theta_{el}|*\vec{rSt}}{|\vec{left}||\vec{sl}||\sin\theta_{sl}| + |\vec{left}||\vec{el}||\sin\theta_{el}|} \f] + * We can cancel the left and we are left with (no word twisting intended): + * \f[ \vec{atx} = \frac{|\vec{sl}||\sin\theta_{sl}|*\vec{rEn} + |\vec{el}||\sin\theta_{el}|*\vec{rSt}}{||\vec{sl}||\sin\theta_{sl}| + |\vec{el}||\sin\theta_{el}|} \f] + * + * \f[ \vec{atx} = \frac{|\vec{sl}||\sin\theta_{sl}|}{|\vec{sl}||\sin\theta_{sl}|+|\vec{el}||\sin\theta_{el}|}*\vec{rEn} + \frac{|\vec{el}||\sin\theta_{el}|}{|\vec{sl}||\sin\theta_{sl}|+|\vec{el}||\sin\theta_{el}|}*\vec{rSt} \f] + * + * What you see here is a simple variant of the midpoint formula that can give us the intersection point. The sin terms when combined with sl or el + * are simply the perpendiculars you see in figure 2 and 3. See how the perpendiculars' relative length change as the intersection point changes on + * the right edge? This is exactly the mechanism used to find out the intersection point and its time on each edge. Look at figure 3, the point I'm + * trying to make is that the red perpendicular's length divided by sum of length of both red and blue perpendiculars is the same factor as the + * (length of the part of the right edge that's to the "right" of intersection) divided by total length of right edge. These ratios are exactly + * what we use to find the intersection point as well as the time of these intersection points. + * + * @param iL Pointer to the left edge's node. + * @param iR Pointer to the right edge's node. + * @param atx The point of intersection. The function sets this if an intersection was detected. + * @param atL The time on the left edge at the intersection point. + * @param atR The time on the right edge at the intersection point. + * @param onlyDiff My best guess about onlyDiff is it stands for "only different". Only detect intersections if + * both edges come from different shapes, otherwise don't bother. + * + * @return true if intersection detected, otherwise false. + */ + bool TesteIntersection(SweepTree *iL, SweepTree *iR, Geom::Point &atx, double &atL, double &atR, bool onlyDiff); + bool TesteIntersection(Shape *iL, Shape *iR, int ilb, int irb, + Geom::Point &atx, double &atL, double &atR, + bool onlyDiff); + bool TesteAdjacency(Shape *iL, int ilb, const Geom::Point atx, int nPt, + bool push); + int PushIncidence(Shape *a, int cb, int pt, double theta); + int CreateIncidence(Shape *a, int cb, int pt); + void AssemblePoints(Shape *a); + + /** + * Sort the points and merge duplicate ones. + * + * Sort the points from st to en - 1. No idea why the parameters were + * set up in this weird way. + * + * The function also sets up newInd to the new indices so everything else + * can update the indices instantly. + * + * @param st The start of the range of points to sort. + * @param en One more than the end of the range of points to sort. + * + * @return If st and en are the same, nothing is done and en is returned. Otherwise, an index one more than the last point + * in the sorted and merged list is returned. So say we gave the sequence of points indexed 2,4,5,6,7 and 4 and 5 were duplicates + * so final sequence would have indices 2,4,5,6 and the function will return 7. + */ + int AssemblePoints(int st, int en); + void AssembleAretes(FillRule directed = fill_nonZero); + + /** + * Add the event in chgts. + * + * The function adds stuff to the edgehead and shapeHead linked lists as well as set the + * leftRnd and rightRnd in swsData of the edges. + * + * @image html livarot-images/situation-from-add-chgt.svg + * + * @param lastPointNo The point that was just added in "this" shape. + * @param lastChgtPt Either lastPointNo if it's the left most point at that y level or whichever point is the left most at the + * same y level as lastPointNo. + * @param shapeHead The linked list from ConvertToShape and Booleen. + * @param edgeHead The linked list from ConvertToShape and Booleen. + * @param type The type of event this is. + * @param lS Pointer to the unique edge's shape if this is edge addition/removal or the left edge's shape if an intersection event. + * @param lB The unique edge (or the left edge if an intersection event). + * @param rS Pointer to the right edge's shape if this is an intersection event. + * @param rB The right edge if this is an intersection event. + */ + void AddChgt(int lastPointNo, int lastChgtPt, Shape *&shapeHead, + int &edgeHead, sTreeChangeType type, Shape *lS, int lB, Shape *rS, + int rB); + + /** + * If there are points that lie on edges, mark this by modifying leftRnd and rightRnd variables. + * + * Please note that an adjacency means a point lying on an edge somewhere. This is checked by + * the function TesteAdjacency. + * + * Before I go into discussing how it works please review the following figure to have an idea + * about how the points look like when this function is called from Shape::ConvertToShape. + * + * @image html livarot-images/lastChgtPt-from-avance.svg + * + * This function has a main loop that runs on all events in chgts. Each event can either be + * an edge addition or edge addition or edge intersection. Each event (chgt) keeps four important + * things. A unique edge associated with the event. For addition/removal it's the main edge that got + * added/removed. For intersection it's the left one. It stores the right edge too in case of an + * intersection. Then there are two pointers to edges to the left and right in the sweepline at the time + * the event happened. This function does four things: + * + * 1. For the unique edge, get the leftRnd and rightRnd. Sets chLeN and chRiN depending on ptNo of the + * event and the leftRnd and rightRnd. See the code to see how this is done. We test all points ranging + * from lastChgtPt to leftRnd-1 for a possible adjacency with this unique edge. If found, we set leftRnd + * of the unique edge accordingly. We also test points in the range rightRnd+1 to lastPointNo (not included) + * for an adjacency and if found, we set rightRnd accordingly. + * 2. Exactly identical thing is done with the right edge (if this is an intersection event). + * 3. Then there is the possibility of having an edge on the left in the sweepline at the time the event happened. + * If that's the case, we do something very special. We check if the left edge's leftRnd is smaller than lastChgtPt, + * if not, it means the leftRnd got updated in the previous iteration of the main loop, thus, we don't need to take + * care of it or do anything about it. If it is smaller than lastChgtPt, we run a loop testing all points in the range + * chLeN..chRiN of having an adjacency with that edge. We update leftRnd and rightRnd of the left edge after doing some + * checks. This process gets repeated for all the edges to the left. + * 4. Same as 3 but for edges to the right. + * + * 3 and 4 are very useful. They deal with cases when you have some other edge's endpoint exactly on some edge and these + * modify leftRnd and rightRnd of the edge so that CheckEdges will later split that edge at that endpoint. For example, + * a shape like: + * SVG path: M 500,200 L 500,800 L 200,800 L 500,500 L 200,200 Z + * + */ + void CheckAdjacencies(int lastPointNo, int lastChgtPt, Shape *shapeHead, int edgeHead); + + /** + * Check if there are edges to draw and draw them. + * + * @image html livarot-images/lastChgtPt-from-avance.svg + * + * @param lastPointNo The point that was just added. See the figure above. + * @param lastChgtPt See the figure above. + * @param a The main shape a. + * @param b The other shape if called from Shape::Booleen. + * @param mod The boolean operation mode if called from Shape::Booleen. + */ + void CheckEdges(int lastPointNo, int lastChgtPt, Shape *a, Shape *b, BooleanOp mod); + + /** + * Do the edge. + * + * That's a very vague thing to say but basically this function checks the leftRnd and rightRnd + * of the edge and if anything needs to be drawn, it draws them. + * + * Most of the code you see in the function body deals with a very rare diagonal case that I suspect would be + * extremely rare. I'll add comments in the code body to further highlight this. But if you ignore all of that + * whatever remains is quite simple. + * + * @image html livarot-images/lastChgtPt-from-avance.svg + * + * This picture shows you how the variables look like when Avance is called by CheckEdges which is called from + * a block of code in Shape::ConvertToShape or Shape::Booleen. You can see that lastPointNo is the point that + * just got added to the list and it has to be the left most, there can't be another point at the same y and + * to the left of lastPointNo. The reason is, the block which calls CheckEdges is called at the leftmost point + * at a particular y. You should also note how lastChgtPt is the left most point but just above lastPointNo + * (smaller y), can't be the same y. + * + * @image html livarot-images/rounded-edge-diagonal-avoid.svg + * + * This image is referred from code comments to help explain a point. + * + * @param lastPointNo The new point that the sweepline just jumped on. No edge processing (adding/removal) has + * been done on the point yet. The point has only been added in "this" shape and this is its index. + * @param lastChgtPt This is hard to visualize but imagine the set of points having a y just smaller than lastPointNo's y + * and now within those points (if there are multiple ones), get the left most one. That's what lastChgtPt will be. + * @param iS The shape to which edge iB belongs. + * @param iB The index of the edge to draw/do. + * @param a Shape a. Not really used. + * @param b Shape b if called ultimately from Shape::Booleen. + * @param mod The mode of boolean operation. + */ + void Avance(int lastPointNo, int lastChgtPt, Shape *iS, int iB, Shape *a, Shape *b, BooleanOp mod); + + /** + * Draw edge to a passed point. + * + * You might ask, don't we need two points to draw an edge. Well iB is the original edge + * passed. The edge stores the last point until which it was drawn. We will simply draw + * an edge between that last point and the point iTo. + * + * Say that lp is the last point drawn and iTo is the new point. The edge will be drawn + * in the following fashion depending on the values of direct and sens + * + * direct & sens : lp -> iTo + * !direct & sens : iTo -> lp + * direct & !sens : iTo -> lp + * !direct & !sens : lp -> iTo + * + * If the edges had back data, the backdata for the new points is carefully calculated by doing + * some maths so the correct t values are found. The function also updates "last point drawn" of + * that edge to the point iTo. There is one more important thing that this function does. If the + * edge iB has a linked list of points associated with it (due to computation of seed winding numbers) + * then we make sure to transfer that linked list of points to the new edge that we just drew and + * destroy the association with the original edge iB. + * + * @param iS The shape to which the original edge belongs. + * @param iB The original edge in shape iS. + * @param iTo The point to draw the edge to. + * @param direct Used to control direction of the edge. + * @param sens Used to control direction of the edge. + */ + void DoEdgeTo(Shape *iS, int iB, int iTo, bool direct, bool sens); + + /** + * Calculates the winding numbers to the left and right of all edges of this shape. + * + * The winding numbers that are calculated are stored in swdData. + * + * The winding number computation algorithm is a very interesting one and I'll get into + * its details too. The algorithm essentially follows sub-graphs to calculate winding + * numbers. A sub-graph is basically a set of edges and points that are connected to each + * other in the sense that you can walk on the edges to move around them. The winding number + * computation algorithm starts at the top most (and left most if there are multiple points at same y). + * There, it is known that the winding number outside the edges is definitely 0 since it's the outer most + * point. However, when you are starting on an edge that's inside the shape, a rectangle inside another one, + * you need to know what outside winding number really is for that point. See the following image to see + * what I mean. + * + * @image html livarot-images/winding-computation-seed.svg + * + * For the outer contour, we know it's definitely 0 but for the inside one it needs to be calculated and it's -1. + * There are two ways to figure out this "seed" winding number. You can either iterate through all edges and calculate + * it manually. This is known as the brutual method. The other method is to use the winding number info left from the + * sweepline algorithm. + * + * I have explained the winding number computation algorithm in detail in the code comments. Once we have a seed, we start + * walking on the edges. Once you have the left and right winding number for the first edge, you can move to its endpoint + * and depending on the relative directions of the edge vectors, you can definitely calculate the winding number for the + * next edge. See the code comments for details on this procedure. + * + * @image html livarot-images/winding-computation.svg + * + * Basically, given the winding numbers to the left and right of the current edge, and the orientation of the next edge + * with this one, we can calculate the winding number of the next edge and then repeat this process. + * + * @param a Useless. + * @param b Useless. + * @param mod Useless. + * @param brutal Should the algorithm use winding number seeds left by the sweepline or brutually compute the seeds? + */ + void GetWindings(Shape *a, Shape *b = nullptr, BooleanOp mod = bool_op_union, bool brutal = false); + + void Validate(); + + /** + * Get the winding number for a point but from the data left by the sweepline algorithm. + * + * @param nPt Index of the point whose finding number we wanna calculate. + */ + int Winding(int nPt) const; + + /** + * Sort all points by their rounded coordinates. + */ + void SortPointsRounded(); + + /** + * Sort points by their rounded coordinates. + * + * Exactly the same as SortPoints but instead of using the actual point coordinates + * rounded coordinates are used. + * + * @param s The start index. + * @param e The end index. + */ + void SortPointsRounded(int s, int e); + + void CreateEdge(int no, float to, float step); + void AvanceEdge(int no, float to, bool exact, float step); + void DestroyEdge(int no, float to, FloatLigne *line); + void AvanceEdge(int no, float to, FloatLigne *line, bool exact, float step); + void DestroyEdge(int no, BitLigne *line); + void AvanceEdge(int no, float to, BitLigne *line, bool exact, float step); + void DestroyEdge(int no, AlphaLigne *line); + void AvanceEdge(int no, float to, AlphaLigne *line, bool exact, float step); + + /** + * Add a contour. + * + * @param dest The pointer to the Path object where we want to add contours. + * @param nbP The total number of path object points in the array orig. + * @param orig A pointer of Path object pointers. These are the original Path objects which were used to fill the directed graph. + * @param startBord The first edge in the contour. + * @param curBord The last edge in the contour. + * @param splitWhenForced TODO: No idea what it does. We never use ForcedPoints in Inkscape so doesn't matter I think. + */ + void AddContour(Path * dest, int nbP, Path **orig, int startBord, + int curBord, bool splitWhenForced); + + int ReFormeLineTo(int bord, int curBord, Path *dest, Path *orig); + int ReFormeArcTo(int bord, int curBord, Path *dest, Path *orig); + int ReFormeCubicTo(int bord, int curBord, Path *dest, Path *orig); + int ReFormeBezierTo(int bord, int curBord, Path *dest, Path *orig); + void ReFormeBezierChunk(const Geom::Point px, const Geom::Point nx, + Path *dest, int inBezier, int nbInterm, + Path *from, int p, double ts, double te); + + int QuickRasterChgEdge(int oBord, int nbord, double x); + int QuickRasterAddEdge(int bord, double x, int guess); + void QuickRasterSubEdge(int bord); + void QuickRasterSwapEdge(int a, int b); + void QuickRasterSort(); + + bool _need_points_sorting; ///< points have been added or removed: we need to sort the points again + bool _need_edges_sorting; ///< edges have been added: maybe they are not ordered clockwise + ///< nota: if you remove an edge, the clockwise order still holds + bool _has_points_data; ///< the pData array is allocated + bool _point_data_initialised;///< the pData array is up to date + bool _has_edges_data; ///< the eData array is allocated + bool _has_sweep_src_data; ///< the swsData array is allocated + bool _has_sweep_dest_data; ///< the swdData array is allocated + bool _has_raster_data; ///< the swrData array is allocated + bool _has_quick_raster_data;///< the swrData array is allocated + bool _has_back_data; //< the ebData array is allocated + bool _has_voronoi_data; + bool _bbox_up_to_date; ///< the leftX/rightX/topY/bottomY are up to date + + std::vector<dg_point> _pts; /*!< The array of points */ + std::vector<dg_arete> _aretes; /*!< The array of edges */ + + // the arrays of temporary data + // these ones are dynamically kept at a length of maxPt or maxAr + std::vector<edge_data> eData; /*!< Extra edge data */ + std::vector<sweep_src_data> swsData; + std::vector<sweep_dest_data> swdData; + std::vector<raster_data> swrData; + std::vector<point_data> pData; /*!< Extra point data */ + + static int CmpQRs(const quick_raster_data &p1, const quick_raster_data &p2) { + if ( fabs(p1.x - p2.x) < 0.00001 ) { + return 0; + } + + return ( ( p1.x < p2.x ) ? -1 : 1 ); + }; + + // edge direction comparison function + + /** + * Edge comparison function. + * + * The function returns +1 when a swap is needed. The arguments + * are arranged in a weird way. Say you have two edges: w x y z and you wanna ask + * if w and x should be swapped, you pass parameters such that ax = x & bx = w. + * + * The explaination of the function in the code body uses this picture to help explain. + * + * @image html livarot-images/edge-sorting.svg + * + * @param ax The right edge in the list before sorting. + * @param bx The left edge in the list before sorting. + * @param as True if the edge of vector ax started from the point. False if it ended there. + * @param bs True if the edge of vector bx started from the point. False if it ended there. + * + * @return A positive number if the arrangement bx ax is wrong and should be swapped. + */ + static int CmpToVert(const Geom::Point ax, const Geom::Point bx, bool as, bool bs); +}; + +/** + * Is the graph Eulerian? + * + * A directed graph is Eulerian if every vertex has equal indegree and outdegree. + * http://mathworld.wolfram.com/EulerianGraph.html + * + * @param s Pointer to the shape object. + * @return True if shape is Eulerian. + */ +bool directedEulerian(Shape const *s); +double distance(Shape const *s, Geom::Point const &p); +bool distanceLessThanOrEqual(Shape const *s, Geom::Point const &p, double const max_l2); + +#endif diff --git a/src/livarot/ShapeDraw.cpp b/src/livarot/ShapeDraw.cpp new file mode 100644 index 0000000..79d070b --- /dev/null +++ b/src/livarot/ShapeDraw.cpp @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* + * ShapeDraw.cpp + * nlivarot + * + * Created by fred on Mon Jun 16 2003. + * + */ + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include "Shape.h" +//#include <ApplicationServices/ApplicationServices.h> + +// debug routine for vizualizing the polygons +void +Shape::Plot (double ix, double iy, double ir, double mx, double my, bool doPoint, + bool edgesNo, bool pointsNo, bool doDir,char* fileName) +{ + FILE* outFile=fopen(fileName,"w+"); +// fprintf(outFile,"\n\n\n"); + fprintf(outFile,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); + fprintf(outFile,"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n"); + fprintf(outFile,"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"); + fprintf(outFile,"<svg:svg\n"); + fprintf(outFile," id=\"svg1\"\n"); + fprintf(outFile," inkscape:version=\"0.38cvs\"\n"); + fprintf(outFile," xmlns:svg=\"http://www.w3.org/2000/svg\"\n"); + fprintf(outFile," xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"); + fprintf(outFile," xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"\n"); + fprintf(outFile," xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); + fprintf(outFile," width=\"210mm\"\n"); + fprintf(outFile," height=\"297mm\"\n"); + fprintf(outFile," sodipodi:docbase=\"/Volumes/Sancho/inkscapecvs\"\n"); + fprintf(outFile," sodipodi:docname=\"/Volumes/Sancho/inkscapecvs/modele.svg\">\n"); + fprintf(outFile," <svg:defs\n"); + fprintf(outFile," id=\"defs3\" />\n"); + fprintf(outFile," <sodipodi:namedview\n"); + fprintf(outFile," id=\"base\"\n"); + fprintf(outFile," pagecolor=\"#ffffff\"\n"); + fprintf(outFile," bordercolor=\"#666666\"\n"); + fprintf(outFile," borderopacity=\"1.0\"\n"); + fprintf(outFile," inkscape:pageopacity=\"0.0\"\n"); + fprintf(outFile," inkscape:pageshadow=\"2\"\n"); + fprintf(outFile," inkscape:zoom=\"0.43415836\"\n"); + fprintf(outFile," inkscape:cx=\"305.25952637\"\n"); + fprintf(outFile," inkscape:cy=\"417.84947271\"\n"); + fprintf(outFile," inkscape:window-width=\"640\"\n"); + fprintf(outFile," inkscape:window-height=\"496\"\n"); + fprintf(outFile," inkscape:window-x=\"20\"\n"); + fprintf(outFile," inkscape:window-y=\"42\" />\n"); + + if ( doPoint ) { + for (int i=0;i<numberOfPoints();i++) { + double ph=(getPoint(i).x[0]-ix)*ir+mx; + double pv=(getPoint(i).x[1]-iy)*ir+my; + fprintf(outFile," <svg:circle cx=\"%f\" cy=\"%f\" r=\"5\" fill=\"none\" stroke=\"red\" stroke-width=\"0.25\" />\n",ph,pv); // localizing ok + } + } + if ( pointsNo ) { + for (int i=0;i<numberOfPoints();i++) { + double ph=(getPoint(i).x[0]-ix)*ir+mx; + double pv=(getPoint(i).x[1]-iy)*ir+my; + fprintf(outFile," <svg:text x=\"%f\" y=\"%f\" font-family=\"Monaco\" font-size=\"5\" fill=\"blue\" >\n",ph-2,pv+1); // localizing ok + fprintf(outFile,"%i\n",i); + fprintf(outFile," </text>\n"); + } + } + { + for (int i=0;i<numberOfEdges();i++) { + int stP=getEdge(i).st; + int enP=getEdge(i).en; + if ( stP < 0 || enP < 0 ) continue; + double sh=(getPoint(stP).x[0]-ix)*ir+mx; + double sv=(getPoint(stP).x[1]-iy)*ir+my; + double eh=(getPoint(enP).x[0]-ix)*ir+mx; + double ev=(getPoint(enP).x[1]-iy)*ir+my; + if ( doDir ) { + double endh=(9*eh+1*sh)/10; + double endv=(9*ev+1*sv)/10; + fprintf(outFile," <svg:line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" stroke=\"black\" stroke-width=\"0.5\" />\n",sh,sv,endh,endv); // localizing ok + } else { + fprintf(outFile," <svg:line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" stroke=\"black\" stroke-width=\"0.5\" />\n",sh,sv,eh,ev); // localizing ok + } + } + } + if ( edgesNo ) { + for (int i=0;i<numberOfEdges();i++) { + int stP=getEdge(i).st; + int enP=getEdge(i).en; + if ( stP < 0 || enP < 0 ) continue; + double sh=(getPoint(stP).x[0]-ix)*ir+mx; + double sv=(getPoint(stP).x[1]-iy)*ir+my; + double eh=(getPoint(enP).x[0]-ix)*ir+mx; + double ev=(getPoint(enP).x[1]-iy)*ir+my; + fprintf(outFile," <svg:text x=\"%f\" y=\"%f\" font-family=\"Monaco\" font-size=\"5\" fill=\"blue\" >\n",(sh+eh)/2+2,(sv+ev)/2); // localizing ok + fprintf(outFile,"%i\n",i); + fprintf(outFile," </text>\n"); + } + } + + fprintf(outFile,"</svg>\n"); + fclose(outFile); + +} diff --git a/src/livarot/ShapeMisc.cpp b/src/livarot/ShapeMisc.cpp new file mode 100644 index 0000000..36e3a23 --- /dev/null +++ b/src/livarot/ShapeMisc.cpp @@ -0,0 +1,1476 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "livarot/Shape.h" +#include "livarot/Path.h" +#include "livarot/path-description.h" +#include <glib.h> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <2geom/point.h> +#include <2geom/affine.h> + +/* + * polygon offset and polyline to path reassembling (when using back data) + */ + +// until i find something better +#define MiscNormalize(v) {\ + double _l=sqrt(dot(v,v)); \ + if ( _l < 0.0000001 ) { \ + v[0]=v[1]=0; \ + } else { \ + v/=_l; \ + }\ +} + +// extracting the contour of an uncrossed polygon: a mere depth first search +// more precisely that's extracting an eulerian path from a graph, but here we want to split +// the polygon into contours and avoid holes. so we take a "next counter-clockwise edge first" approach +// (make a checkboard and extract its contours to see the difference) +void +Shape::ConvertToForme (Path * dest) +{ + // this function is quite similar to Shape::GetWindings so please check it out + // first to learn the overall technique and I'll make sure to comment the parts + // that are different + + if (numberOfPoints() <= 1 || numberOfEdges() <= 1) + return; + + // prepare + dest->Reset (); + + MakePointData (true); + MakeEdgeData (true); + MakeSweepDestData (true); + + for (int i = 0; i < numberOfPoints(); i++) + { + pData[i].rx[0] = Round (getPoint(i).x[0]); + pData[i].rx[1] = Round (getPoint(i).x[1]); + } + for (int i = 0; i < numberOfEdges(); i++) + { + eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx; + } + + // sort edge clockwise, with the closest after midnight being first in the doubly-linked list + // that's vital to the algorithm... + SortEdges (); + + // depth-first search implies: we make a stack of edges traversed. + // precParc: previous in the stack + // suivParc: next in the stack + for (int i = 0; i < numberOfEdges(); i++) + { + swdData[i].misc = nullptr; + swdData[i].precParc = swdData[i].suivParc = -1; + } + + int searchInd = 0; + + int lastPtUsed = 0; + do + { + // first get a starting point, and a starting edge + // -> take the upper left point, and take its first edge + // points traversed have swdData[].misc != 0, so it's easy + int startBord = -1; + { + int fi = 0; + for (fi = lastPtUsed; fi < numberOfPoints(); fi++) + { + if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == nullptr) + break; + } + lastPtUsed = fi + 1; + if (fi < numberOfPoints()) + { + int bestB = getPoint(fi).incidentEdge[FIRST]; + // we get the edge that starts at this point since we wanna follow the direction of the edges + while (bestB >= 0 && getEdge(bestB).st != fi) + bestB = NextAt (fi, bestB); + if (bestB >= 0) + { + startBord = bestB; + dest->MoveTo (getPoint(getEdge(startBord).en).x); + } + } + } + // and walk the graph, doing contours when needed + if (startBord >= 0) + { + // parcours en profondeur pour mettre les leF et riF a leurs valeurs + swdData[startBord].misc = (void *) 1; + // printf("part de %d\n",startBord); + int curBord = startBord; + bool back = false; // a variable that if true, means we are back tracking + swdData[curBord].precParc = -1; + swdData[curBord].suivParc = -1; + do + { + int cPt = getEdge(curBord).en; // get the end point, we want to follow the direction of the edge + int nb = curBord; + // printf("de curBord= %d au point %i -> ",curBord,cPt); + // get next edge + do + { + int nnb = CycleNextAt (cPt, nb); // get the next (clockwise) edge (I don't see why the comment at the top says anti clockwise edge first) + if (nnb == nb) + { + // cul-de-sac + nb = -1; + break; + } + nb = nnb; + if (nb < 0 || nb == curBord) // if we got to the same edge, we break, cuz now we need to back track + break; + } + while (swdData[nb].misc != nullptr || getEdge(nb).st != cPt); // keep finding a new edge until we find an edge that we haven't seen or an edge + // that starts at the point + + if (nb < 0 || nb == curBord) + { + // if we are here, means there was no new edge, so we start back tracking + // no next edge: end of this contour, we get back + if (back == false) + dest->Close (); + back = true; + // retour en arriere + curBord = swdData[curBord].precParc; // set previous edge to current edge and back is true to indicate we are backtracking + // printf("retour vers %d\n",curBord); + if (curBord < 0) // break if no edge exists before this one + break; + } + else + { + // did we get this new edge after we started backtracking? if yes, we need a moveTo + // new edge, maybe for a new contour + if (back) + { + // we were backtracking, so if we have a new edge, that means we're creating a new contour + dest->MoveTo (getPoint(cPt).x); + back = false; // we are no longer backtracking, we will follow this new edge now + } + swdData[nb].misc = (void *) 1; + swdData[nb].ind = searchInd++; + swdData[nb].precParc = curBord; + swdData[curBord].suivParc = nb; + curBord = nb; + // printf("suite %d\n",curBord); + { + // add that edge + dest->LineTo (getPoint(getEdge(nb).en).x); // add a line segment + } + } + } + while (true /*swdData[curBord].precParc >= 0 */ ); + // fin du cas non-oriente + } + } + while (lastPtUsed < numberOfPoints()); + + MakePointData (false); + MakeEdgeData (false); + MakeSweepDestData (false); +} + +// same as before, but each time we have a contour, try to reassemble the segments on it to make chunks of +// the original(s) path(s) +// originals are in the orig array, whose size is nbP +void +Shape::ConvertToForme (Path * dest, int nbP, Path * *orig, bool splitWhenForced) +{ + // the function is similar to the other version of ConvertToForme, I'm adding comments + // where there are significant differences to explain + if (numberOfPoints() <= 1 || numberOfEdges() <= 1) + return; +// if (Eulerian (true) == false) +// return; + + if (_has_back_data == false) + { + ConvertToForme (dest); + return; + } + + dest->Reset (); + + MakePointData (true); + MakeEdgeData (true); + MakeSweepDestData (true); + + for (int i = 0; i < numberOfPoints(); i++) + { + pData[i].rx[0] = Round (getPoint(i).x[0]); + pData[i].rx[1] = Round (getPoint(i).x[1]); + } + for (int i = 0; i < numberOfEdges(); i++) + { + eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx; + } + + SortEdges (); + + for (int i = 0; i < numberOfEdges(); i++) + { + swdData[i].misc = nullptr; + swdData[i].precParc = swdData[i].suivParc = -1; + } + + int searchInd = 0; + + int lastPtUsed = 0; + do + { + int startBord = -1; + { + int fi = 0; + for (fi = lastPtUsed; fi < numberOfPoints(); fi++) + { + if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == nullptr) + break; + } + lastPtUsed = fi + 1; + if (fi < numberOfPoints()) + { + int bestB = getPoint(fi).incidentEdge[FIRST]; + while (bestB >= 0 && getEdge(bestB).st != fi) + bestB = NextAt (fi, bestB); + if (bestB >= 0) + { + startBord = bestB; // no moveTo here unlike the other ConvertToForme because we want AddContour to do all the contour extraction stuff + } + } + } + if (startBord >= 0) + { + // parcours en profondeur pour mettre les leF et riF a leurs valeurs + swdData[startBord].misc = (void *) 1; + //printf("part de %d\n",startBord); + int curBord = startBord; + bool back = false; + swdData[curBord].precParc = -1; + swdData[curBord].suivParc = -1; + int curStartPt=getEdge(curBord).st; // we record the start point of the current edge (actually the start edge at the moment) + do + { + int cPt = getEdge(curBord).en; + int nb = curBord; + //printf("de curBord= %d au point %i -> ",curBord,cPt); + do + { + int nnb = CycleNextAt (cPt, nb); + if (nnb == nb) + { + // cul-de-sac + nb = -1; + break; + } + nb = nnb; + if (nb < 0 || nb == curBord) // if we get the same edge, we break + break; + } + while (swdData[nb].misc != nullptr || getEdge(nb).st != cPt); + + if (nb < 0 || nb == curBord) + { // we are backtracking + if (back == false) // if we weren't backtracking + { + if (curBord == startBord || curBord < 0) // is the current edge the one we started on? + { + // probleme -> on vire le moveto + // dest->descr_nb--; + } + else // if not, we now have a contour to add + { + swdData[curBord].suivParc = -1; + AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced); + } + // dest->Close(); + } + back = true; // we are backtracking now + // retour en arriere + curBord = swdData[curBord].precParc; // get the previous edge + //printf("retour vers %d\n",curBord); + if (curBord < 0) // if no previous edge, we break + break; + } + else + { + if (back) + { // if we are backtracking, now we go forward (since we have found a new edge to explore) + back = false; + startBord = nb; + curStartPt=getEdge(nb).st; // reset curStartPt to this as a new contour is starting here + } else { + if ( getEdge(curBord).en == curStartPt ) { // if we are going forward and the endpoint of this edge is the actual start point + //printf("contour %i ",curStartPt); + swdData[curBord].suivParc = -1; // why tho? seems useless since we set it right after this block ends + AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced); // add the contour + startBord=nb; // set startBord to this edge + } + } + swdData[nb].misc = (void *) 1; + swdData[nb].ind = searchInd++; + swdData[nb].precParc = curBord; + swdData[curBord].suivParc = nb; + curBord = nb; + //printf("suite %d\n",curBord); + } + } + while (true /*swdData[curBord].precParc >= 0 */ ); + // fin du cas non-oriente + } + } + while (lastPtUsed < numberOfPoints()); + + MakePointData (false); + MakeEdgeData (false); + MakeSweepDestData (false); +} +void +Shape::ConvertToFormeNested (Path * dest, int nbP, Path * *orig, int /*wildPath*/,int &nbNest,int *&nesting,int *&contStart,bool splitWhenForced) +{ + nesting=nullptr; + contStart=nullptr; + nbNest=0; + + if (numberOfPoints() <= 1 || numberOfEdges() <= 1) + return; + // if (Eulerian (true) == false) + // return; + + if (_has_back_data == false) + { + ConvertToForme (dest); + return; + } + + dest->Reset (); + +// MakePointData (true); + MakeEdgeData (true); + MakeSweepDestData (true); + + for (int i = 0; i < numberOfPoints(); i++) + { + pData[i].rx[0] = Round (getPoint(i).x[0]); + pData[i].rx[1] = Round (getPoint(i).x[1]); + } + for (int i = 0; i < numberOfEdges(); i++) + { + eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx; + } + + SortEdges (); + + for (int i = 0; i < numberOfEdges(); i++) + { + swdData[i].misc = nullptr; + swdData[i].precParc = swdData[i].suivParc = -1; + } + + int searchInd = 0; + + int lastPtUsed = 0; + int parentContour=-1; + do + { + int childEdge = -1; + bool foundChild = false; + int startBord = -1; + { + int fi = 0; + for (fi = lastPtUsed; fi < numberOfPoints(); fi++) + { + if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == nullptr) + break; + } + { + if (pData.size()<= fi || fi == numberOfPoints()) { + parentContour=-1; + } else { + int askTo = pData[fi].askForWindingB; + if (askTo < 0 || askTo >= numberOfEdges() ) { + parentContour=-1; + } else { + if (getEdge(askTo).prevS >= 0) { + parentContour = GPOINTER_TO_INT(swdData[askTo].misc); + parentContour-=1; // pour compenser le decalage + } + childEdge = getPoint(fi).incidentEdge[FIRST]; + } + } + } + lastPtUsed = fi + 1; + if (fi < numberOfPoints()) + { + int bestB = getPoint(fi).incidentEdge[FIRST]; + while (bestB >= 0 && getEdge(bestB).st != fi) + bestB = NextAt (fi, bestB); + if (bestB >= 0) + { + startBord = bestB; + } + } + } + if (startBord >= 0) + { + // parcours en profondeur pour mettre les leF et riF a leurs valeurs + swdData[startBord].misc = (void *)(intptr_t)(1 + nbNest); + if (startBord == childEdge) { + foundChild = true; + } + //printf("part de %d\n",startBord); + int curBord = startBord; + bool back = false; + swdData[curBord].precParc = -1; + swdData[curBord].suivParc = -1; + int curStartPt=getEdge(curBord).st; + do + { + int cPt = getEdge(curBord).en; + int nb = curBord; + //printf("de curBord= %d au point %i -> ",curBord,cPt); + do + { + int nnb = CycleNextAt (cPt, nb); + if (nnb == nb) + { + // cul-de-sac + nb = -1; + break; + } + nb = nnb; + if (nb < 0 || nb == curBord) + break; + } + while (swdData[nb].misc != nullptr || getEdge(nb).st != cPt); + + if (nb < 0 || nb == curBord) + { + if (back == false) + { + if (curBord == startBord || curBord < 0) + { + // probleme -> on vire le moveto + // dest->descr_nb--; + } + else + { +// bool escapePath=false; +// int tb=curBord; +// while ( tb >= 0 && tb < numberOfEdges() ) { +// if ( ebData[tb].pathID == wildPath ) { +// escapePath=true; +// break; +// } +// tb=swdData[tb].precParc; +// } + nesting=(int*)g_realloc(nesting,(nbNest+1)*sizeof(int)); + contStart=(int*)g_realloc(contStart,(nbNest+1)*sizeof(int)); + contStart[nbNest]=dest->descr_cmd.size(); + if (foundChild) { + nesting[nbNest++]=parentContour; + foundChild = false; + } else { + nesting[nbNest++]=-1; // contient des bouts de coupure -> a part + } + swdData[curBord].suivParc = -1; + AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced); + } + // dest->Close(); + } + back = true; + // retour en arriere + curBord = swdData[curBord].precParc; + //printf("retour vers %d\n",curBord); + if (curBord < 0) + break; + } + else + { + if (back) + { + back = false; + startBord = nb; + curStartPt=getEdge(nb).st; + } else { + if ( getEdge(curBord).en == curStartPt ) { + //printf("contour %i ",curStartPt); + +// bool escapePath=false; +// int tb=curBord; +// while ( tb >= 0 && tb < numberOfEdges() ) { +// if ( ebData[tb].pathID == wildPath ) { +// escapePath=true; +// break; +// } +// tb=swdData[tb].precParc; +// } + nesting=(int*)g_realloc(nesting,(nbNest+1)*sizeof(int)); + contStart=(int*)g_realloc(contStart,(nbNest+1)*sizeof(int)); + contStart[nbNest]=dest->descr_cmd.size(); + if (foundChild) { + nesting[nbNest++]=parentContour; + foundChild = false; + } else { + nesting[nbNest++]=-1; // contient des bouts de coupure -> a part + } + swdData[curBord].suivParc = -1; + AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced); + startBord=nb; + } + } + swdData[nb].misc = (void *)(intptr_t)(1 + nbNest); + swdData[nb].ind = searchInd++; + swdData[nb].precParc = curBord; + swdData[curBord].suivParc = nb; + curBord = nb; + if (nb == childEdge) { + foundChild = true; + } + //printf("suite %d\n",curBord); + } + } + while (true /*swdData[curBord].precParc >= 0 */ ); + // fin du cas non-oriente + } + } + while (lastPtUsed < numberOfPoints()); + + MakePointData (false); + MakeEdgeData (false); + MakeSweepDestData (false); +} + + +int +Shape::MakeTweak (int mode, Shape *a, double power, JoinType join, double miter, bool do_profile, Geom::Point c, Geom::Point vector, double radius, Geom::Affine *i2doc) +{ + Reset (0, 0); + MakeBackData(a->_has_back_data); + + bool done_something = false; + + if (power == 0) + { + _pts = a->_pts; + if (numberOfPoints() > maxPt) + { + maxPt = numberOfPoints(); + if (_has_points_data) { + pData.resize(maxPt); + _point_data_initialised = false; + _bbox_up_to_date = false; + } + } + + _aretes = a->_aretes; + if (numberOfEdges() > maxAr) + { + maxAr = numberOfEdges(); + if (_has_edges_data) + eData.resize(maxAr); + if (_has_sweep_src_data) + swsData.resize(maxAr); + if (_has_sweep_dest_data) + swdData.resize(maxAr); + if (_has_raster_data) + swrData.resize(maxAr); + if (_has_back_data) + ebData.resize(maxAr); + } + return 0; + } + if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1 || a->type != shape_polygon) + return shape_input_err; + + a->SortEdges (); + + a->MakeSweepDestData (true); + a->MakeSweepSrcData (true); + + for (int i = 0; i < a->numberOfEdges(); i++) + { + int stB = -1, enB = -1; + if (power <= 0 || mode == tweak_mode_push || mode == tweak_mode_repel || mode == tweak_mode_roughen) { + stB = a->CyclePrevAt (a->getEdge(i).st, i); + enB = a->CycleNextAt (a->getEdge(i).en, i); + } else { + stB = a->CycleNextAt (a->getEdge(i).st, i); + enB = a->CyclePrevAt (a->getEdge(i).en, i); + } + + Geom::Point stD = a->getEdge(stB).dx; + Geom::Point seD = a->getEdge(i).dx; + Geom::Point enD = a->getEdge(enB).dx; + + double stL = sqrt (dot(stD,stD)); + double seL = sqrt (dot(seD,seD)); + //double enL = sqrt (dot(enD,enD)); + MiscNormalize (stD); + MiscNormalize (enD); + MiscNormalize (seD); + + Geom::Point ptP; + int stNo, enNo; + ptP = a->getPoint(a->getEdge(i).st).x; + + Geom::Point to_center = ptP * (*i2doc) - c; + Geom::Point to_center_normalized = (1/Geom::L2(to_center)) * to_center; + + double this_power; + if (do_profile && i2doc) { + double alpha = 1; + double x; + if (mode == tweak_mode_repel) { + x = (Geom::L2(to_center)/radius); + } else { + x = (Geom::L2(ptP * (*i2doc) - c)/radius); + } + if (x > 1) { + this_power = 0; + } else if (x <= 0) { + if (mode == tweak_mode_repel) { + this_power = 0; + } else { + this_power = power; + } + } else { + this_power = power * (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5); + } + } else { + if (mode == tweak_mode_repel) { + this_power = 0; + } else { + this_power = power; + } + } + + if (this_power != 0) + done_something = true; + + double scaler = 1 / (*i2doc).descrim(); + + Geom::Point this_vec(0,0); + if (mode == tweak_mode_push) { + Geom::Affine tovec (*i2doc); + tovec[4] = tovec[5] = 0; + tovec = tovec.inverse(); + this_vec = this_power * (vector * tovec) ; + } else if (mode == tweak_mode_repel) { + this_vec = this_power * scaler * to_center_normalized; + } else if (mode == tweak_mode_roughen) { + double angle = g_random_double_range(0, 2*M_PI); + this_vec = g_random_double_range(0, 1) * this_power * scaler * Geom::Point(sin(angle), cos(angle)); + } + + int usePathID=-1; + int usePieceID=0; + double useT=0.0; + if ( a->_has_back_data ) { + if ( a->ebData[i].pathID >= 0 && a->ebData[stB].pathID == a->ebData[i].pathID && a->ebData[stB].pieceID == a->ebData[i].pieceID + && a->ebData[stB].tEn == a->ebData[i].tSt ) { + usePathID=a->ebData[i].pathID; + usePieceID=a->ebData[i].pieceID; + useT=a->ebData[i].tSt; + } else { + usePathID=a->ebData[i].pathID; + usePieceID=0; + useT=0; + } + } + + if (mode == tweak_mode_push || mode == tweak_mode_repel || mode == tweak_mode_roughen) { + Path::DoLeftJoin (this, 0, join, ptP+this_vec, stD+this_vec, seD+this_vec, miter, stL, seL, + stNo, enNo,usePathID,usePieceID,useT); + a->swsData[i].stPt = enNo; + a->swsData[stB].enPt = stNo; + } else { + if (power > 0) { + Path::DoRightJoin (this, this_power * scaler, join, ptP, stD, seD, miter, stL, seL, + stNo, enNo,usePathID,usePieceID,useT); + a->swsData[i].stPt = enNo; + a->swsData[stB].enPt = stNo; + } else { + Path::DoLeftJoin (this, -this_power * scaler, join, ptP, stD, seD, miter, stL, seL, + stNo, enNo,usePathID,usePieceID,useT); + a->swsData[i].stPt = enNo; + a->swsData[stB].enPt = stNo; + } + } + } + + if (power < 0 || mode == tweak_mode_push || mode == tweak_mode_repel || mode == tweak_mode_roughen) + { + for (int i = 0; i < numberOfEdges(); i++) + Inverse (i); + } + + if ( _has_back_data ) { + for (int i = 0; i < a->numberOfEdges(); i++) + { + int nEd=AddEdge (a->swsData[i].stPt, a->swsData[i].enPt); + ebData[nEd]=a->ebData[i]; + } + } else { + for (int i = 0; i < a->numberOfEdges(); i++) + { + AddEdge (a->swsData[i].stPt, a->swsData[i].enPt); + } + } + + a->MakeSweepSrcData (false); + a->MakeSweepDestData (false); + + return (done_something? 0 : shape_nothing_to_do); +} + + +// offsets +// take each edge, offset it, and make joins with previous at edge start and next at edge end (previous and +// next being with respect to the clockwise order) +// you gotta be very careful with the join, as anything but the right one will fuck everything up +// see PathStroke.cpp for the "right" joins +int +Shape::MakeOffset (Shape * a, double dec, JoinType join, double miter, bool do_profile, double cx, double cy, double radius, Geom::Affine *i2doc) +{ + Reset (0, 0); + MakeBackData(a->_has_back_data); + + bool done_something = false; + + if (dec == 0) + { + _pts = a->_pts; + if (numberOfPoints() > maxPt) + { + maxPt = numberOfPoints(); + if (_has_points_data) { + pData.resize(maxPt); + _point_data_initialised = false; + _bbox_up_to_date = false; + } + } + + _aretes = a->_aretes; + if (numberOfEdges() > maxAr) + { + maxAr = numberOfEdges(); + if (_has_edges_data) + eData.resize(maxAr); + if (_has_sweep_src_data) + swsData.resize(maxAr); + if (_has_sweep_dest_data) + swdData.resize(maxAr); + if (_has_raster_data) + swrData.resize(maxAr); + if (_has_back_data) + ebData.resize(maxAr); + } + return 0; + } + if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1 || a->type != shape_polygon) + return shape_input_err; + + a->SortEdges (); + + a->MakeSweepDestData (true); + a->MakeSweepSrcData (true); + + for (int i = 0; i < a->numberOfEdges(); i++) + { + // int stP=a->swsData[i].stPt/*,enP=a->swsData[i].enPt*/; + int stB = -1, enB = -1; + if (dec > 0) + { + stB = a->CycleNextAt (a->getEdge(i).st, i); + enB = a->CyclePrevAt (a->getEdge(i).en, i); + } + else + { + stB = a->CyclePrevAt (a->getEdge(i).st, i); + enB = a->CycleNextAt (a->getEdge(i).en, i); + } + + Geom::Point stD = a->getEdge(stB).dx; + Geom::Point seD = a->getEdge(i).dx; + Geom::Point enD = a->getEdge(enB).dx; + + double stL = sqrt (dot(stD,stD)); + double seL = sqrt (dot(seD,seD)); + //double enL = sqrt (dot(enD,enD)); + MiscNormalize (stD); + MiscNormalize (enD); + MiscNormalize (seD); + + Geom::Point ptP; + int stNo, enNo; + ptP = a->getPoint(a->getEdge(i).st).x; + + double this_dec; + if (do_profile && i2doc) { + double alpha = 1; + double x = (Geom::L2(ptP * (*i2doc) - Geom::Point(cx,cy))/radius); + if (x > 1) { + this_dec = 0; + } else if (x <= 0) { + this_dec = dec; + } else { + this_dec = dec * (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5); + } + } else { + this_dec = dec; + } + + if (this_dec != 0) + done_something = true; + + int usePathID=-1; + int usePieceID=0; + double useT=0.0; + if ( a->_has_back_data ) { + if ( a->ebData[i].pathID >= 0 && a->ebData[stB].pathID == a->ebData[i].pathID && a->ebData[stB].pieceID == a->ebData[i].pieceID + && a->ebData[stB].tEn == a->ebData[i].tSt ) { + usePathID=a->ebData[i].pathID; + usePieceID=a->ebData[i].pieceID; + useT=a->ebData[i].tSt; + } else { + usePathID=a->ebData[i].pathID; + usePieceID=0; + useT=0; + } + } + if (dec > 0) + { + Path::DoRightJoin (this, this_dec, join, ptP, stD, seD, miter, stL, seL, + stNo, enNo,usePathID,usePieceID,useT); + a->swsData[i].stPt = enNo; + a->swsData[stB].enPt = stNo; + } + else + { + Path::DoLeftJoin (this, -this_dec, join, ptP, stD, seD, miter, stL, seL, + stNo, enNo,usePathID,usePieceID,useT); + a->swsData[i].stPt = enNo; + a->swsData[stB].enPt = stNo; + } + } + + if (dec < 0) + { + for (int i = 0; i < numberOfEdges(); i++) + Inverse (i); + } + + if ( _has_back_data ) { + for (int i = 0; i < a->numberOfEdges(); i++) + { + int nEd=AddEdge (a->swsData[i].stPt, a->swsData[i].enPt); + ebData[nEd]=a->ebData[i]; + } + } else { + for (int i = 0; i < a->numberOfEdges(); i++) + { + AddEdge (a->swsData[i].stPt, a->swsData[i].enPt); + } + } + + a->MakeSweepSrcData (false); + a->MakeSweepDestData (false); + + return (done_something? 0 : shape_nothing_to_do); +} + + + +// we found a contour, now reassemble the edges on it, instead of dumping them in the Path "dest" as a +// polyline. since it was a DFS, the precParc and suivParc make a nice doubly-linked list of the edges in +// the contour. the first and last edges of the contour are startBord and curBord +void +Shape::AddContour (Path * dest, int nbP, Path * *orig, int startBord, int curBord, bool splitWhenForced) +{ + int bord = startBord; // start at the first edge + + { + dest->MoveTo (getPoint(getEdge(bord).st).x); // do a MoveTo to the starting point + } + + while (bord >= 0) + { + int nPiece = ebData[bord].pieceID; // get the piece and the path ID + int nPath = ebData[bord].pathID; + + if (nPath < 0 || nPath >= nbP || orig[nPath] == nullptr) // if the path id is invalid in any way, just add the edge as line segment + { + // segment batard + dest->LineTo (getPoint(getEdge(bord).en).x); + bord = swdData[bord].suivParc; + } + else + { + Path *from = orig[nPath]; // Get pointer to the path object where this edge comes from + if (nPiece < 0 || nPiece >= int(from->descr_cmd.size())) // if the piece id is invalid in anyway, again add the edge as just a line segment + { + // segment batard + dest->LineTo (getPoint(getEdge(bord).en).x); + bord = swdData[bord].suivParc; + } + else + { + int nType = from->descr_cmd[nPiece]->getType(); // get the type of the description + if (nType == descr_close || nType == descr_moveto // if a moveTo, close or forced, we just do a lineTo + || nType == descr_forced) + { + // devrait pas arriver + dest->LineTo (getPoint(getEdge(bord).en).x); + bord = swdData[bord].suivParc; + } + else if (nType == descr_lineto) // for the other ones, call the appropriate function, bord gets the return value which tells us the next edge we should look at + { + bord = ReFormeLineTo (bord, curBord, dest, from); + } + else if (nType == descr_arcto) + { + bord = ReFormeArcTo (bord, curBord, dest, from); + } + else if (nType == descr_cubicto) + { + bord = ReFormeCubicTo (bord, curBord, dest, from); + } + else if (nType == descr_bezierto) + { + PathDescrBezierTo* nBData = + dynamic_cast<PathDescrBezierTo *>(from->descr_cmd[nPiece]); + + if (nBData->nb == 0) + { + bord = ReFormeLineTo (bord, curBord, dest, from); + } + else + { + bord = ReFormeBezierTo (bord, curBord, dest, from); + } + } + else if (nType == descr_interm_bezier) + { + bord = ReFormeBezierTo (bord, curBord, dest, from); + } + else + { + // devrait pas arriver non plus + dest->LineTo (getPoint(getEdge(bord).en).x); + bord = swdData[bord].suivParc; + } + // Hard to say what this block of code does really + // ForcedPoint gets added in dest but then while dumping the SVG d attribute it has no use so never appears + // You can see the code for PathDescrForced in path-description.h to confirm this + // Also, I'm not sure if there is anything that increases the degree so oldDegree and totalDegree() would differ + // at all. + if (bord >= 0 && getPoint(getEdge(bord).st).totalDegree() > 2 ) { + dest->ForcePoint (); + } else if ( bord >= 0 && getPoint(getEdge(bord).st).oldDegree > 2 && getPoint(getEdge(bord).st).totalDegree() == 2) { + if ( splitWhenForced ) { + // pour les coupures + dest->ForcePoint (); + } else { + if ( _has_back_data ) { + int prevEdge=getPoint(getEdge(bord).st).incidentEdge[FIRST]; + int nextEdge=getPoint(getEdge(bord).st).incidentEdge[LAST]; + if ( getEdge(prevEdge).en != getEdge(bord).st ) { + int swai=prevEdge;prevEdge=nextEdge;nextEdge=swai; + } + if ( ebData[prevEdge].pieceID == ebData[nextEdge].pieceID && ebData[prevEdge].pathID == ebData[nextEdge].pathID ) { + if ( fabs(ebData[prevEdge].tEn-ebData[nextEdge].tSt) < 0.05 ) { + } else { + dest->ForcePoint (); + } + } else { + dest->ForcePoint (); + } + } else { + dest->ForcePoint (); + } + } + } + } + } + } + dest->Close (); +} + +int +Shape::ReFormeLineTo (int bord, int /*curBord*/, Path * dest, Path * /*orig*/) +{ + int nPiece = ebData[bord].pieceID; + int nPath = ebData[bord].pathID; + double /*ts=ebData[bord].tSt, */ te = ebData[bord].tEn; + Geom::Point nx = getPoint(getEdge(bord).en).x; + bord = swdData[bord].suivParc; + while (bord >= 0) + { + if (getPoint(getEdge(bord).st).totalDegree() > 2 + || getPoint(getEdge(bord).st).oldDegree > 2) + { + break; + } + if (ebData[bord].pieceID == nPiece && ebData[bord].pathID == nPath) + { + if (fabs (te - ebData[bord].tSt) > 0.0001) + break; + nx = getPoint(getEdge(bord).en).x; + te = ebData[bord].tEn; + } + else + { + break; + } + bord = swdData[bord].suivParc; + } + { + dest->LineTo (nx); + } + return bord; +} + +int +Shape::ReFormeArcTo (int bord, int /*curBord*/, Path * dest, Path * from) +{ + int nPiece = ebData[bord].pieceID; + int nPath = ebData[bord].pathID; + double ts = ebData[bord].tSt, te = ebData[bord].tEn; + // double px=pts[getEdge(bord).st].x,py=pts[getEdge(bord).st].y; + Geom::Point nx = getPoint(getEdge(bord).en).x; + bord = swdData[bord].suivParc; + while (bord >= 0) + { + if (getPoint(getEdge(bord).st).totalDegree() > 2 + || getPoint(getEdge(bord).st).oldDegree > 2) + { + break; + } + if (ebData[bord].pieceID == nPiece && ebData[bord].pathID == nPath) + { + if (fabs (te - ebData[bord].tSt) > 0.0001) + { + break; + } + nx = getPoint(getEdge(bord).en).x; + te = ebData[bord].tEn; + } + else + { + break; + } + bord = swdData[bord].suivParc; + } + double sang, eang; + PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo *>(from->descr_cmd[nPiece]); + bool nLarge = nData->large; + bool nClockwise = nData->clockwise; + Path::ArcAngles (from->PrevPoint (nPiece - 1), nData->p,nData->rx,nData->ry,nData->angle*M_PI/180.0, nLarge, nClockwise, sang, eang); + if (nClockwise) + { + if (sang < eang) + sang += 2 * M_PI; + } + else + { + if (sang > eang) + sang -= 2 * M_PI; + } + double delta = eang - sang; + double ndelta = delta * (te - ts); + if (ts > te) + nClockwise = !nClockwise; + if (ndelta < 0) + ndelta = -ndelta; + if (ndelta > M_PI) + nLarge = true; + else + nLarge = false; + /* if ( delta < 0 ) delta=-delta; + if ( ndelta < 0 ) ndelta=-ndelta; + if ( ( delta < M_PI && ndelta < M_PI ) || ( delta >= M_PI && ndelta >= M_PI ) ) { + if ( ts < te ) { + } else { + nClockwise=!(nClockwise); + } + } else { + // nLarge=!(nLarge); + nLarge=false; // c'est un sous-segment -> l'arc ne peut que etre plus petit + if ( ts < te ) { + } else { + nClockwise=!(nClockwise); + } + }*/ + { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(from->descr_cmd[nPiece]); + dest->ArcTo (nx, nData->rx,nData->ry,nData->angle, nLarge, nClockwise); + } + return bord; +} + +int +Shape::ReFormeCubicTo (int bord, int /*curBord*/, Path * dest, Path * from) +{ + int nPiece = ebData[bord].pieceID; + int nPath = ebData[bord].pathID; + double ts = ebData[bord].tSt, te = ebData[bord].tEn; + Geom::Point nx = getPoint(getEdge(bord).en).x; + bord = swdData[bord].suivParc; + while (bord >= 0) + { + if (getPoint(getEdge(bord).st).totalDegree() > 2 + || getPoint(getEdge(bord).st).oldDegree > 2) + { + break; + } + if (ebData[bord].pieceID == nPiece && ebData[bord].pathID == nPath) + { + if (fabs (te - ebData[bord].tSt) > 0.0001) + { + break; + } + nx = getPoint(getEdge(bord).en).x; + te = ebData[bord].tEn; + } + else + { + break; + } + bord = swdData[bord].suivParc; + } + Geom::Point prevx = from->PrevPoint (nPiece - 1); + + Geom::Point sDx, eDx; + { + PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(from->descr_cmd[nPiece]); + Path::CubicTangent (ts, sDx, prevx,nData->start,nData->p,nData->end); + Path::CubicTangent (te, eDx, prevx,nData->start,nData->p,nData->end); + } + sDx *= (te - ts); + eDx *= (te - ts); + { + dest->CubicTo (nx,sDx,eDx); + } + return bord; +} + +int +Shape::ReFormeBezierTo (int bord, int /*curBord*/, Path * dest, Path * from) +{ + int nPiece = ebData[bord].pieceID; + int nPath = ebData[bord].pathID; + double ts = ebData[bord].tSt, te = ebData[bord].tEn; + int ps = nPiece, pe = nPiece; + Geom::Point px = getPoint(getEdge(bord).st).x; + Geom::Point nx = getPoint(getEdge(bord).en).x; + int inBezier = -1, nbInterm = -1; + int typ; + typ = from->descr_cmd[nPiece]->getType(); + PathDescrBezierTo *nBData = nullptr; + if (typ == descr_bezierto) + { + nBData = dynamic_cast<PathDescrBezierTo *>(from->descr_cmd[nPiece]); + inBezier = nPiece; + nbInterm = nBData->nb; + } + else + { + int n = nPiece - 1; + while (n > 0) + { + typ = from->descr_cmd[n]->getType(); + if (typ == descr_bezierto) + { + inBezier = n; + nBData = dynamic_cast<PathDescrBezierTo *>(from->descr_cmd[n]); + nbInterm = nBData->nb; + break; + } + n--; + } + if (inBezier < 0) + { + bord = swdData[bord].suivParc; + dest->LineTo (nx); + return bord; + } + } + bord = swdData[bord].suivParc; + while (bord >= 0) + { + if (getPoint(getEdge(bord).st).totalDegree() > 2 + || getPoint(getEdge(bord).st).oldDegree > 2) + { + break; + } + if (ebData[bord].pathID == nPath) + { + if (ebData[bord].pieceID < inBezier + || ebData[bord].pieceID >= inBezier + nbInterm) + break; + if (ebData[bord].pieceID == pe + && fabs (te - ebData[bord].tSt) > 0.0001) + break; + if (ebData[bord].pieceID != pe + && (ebData[bord].tSt > 0.0001 && ebData[bord].tSt < 0.9999)) + break; + if (ebData[bord].pieceID != pe && (te > 0.0001 && te < 0.9999)) + break; + nx = getPoint(getEdge(bord).en).x; + te = ebData[bord].tEn; + pe = ebData[bord].pieceID; + } + else + { + break; + } + bord = swdData[bord].suivParc; + } + + g_return_val_if_fail(nBData != nullptr, 0); + + if (pe == ps) + { + ReFormeBezierChunk (px, nx, dest, inBezier, nbInterm, from, ps, + ts, te); + } + else if (ps < pe) + { + if (ts < 0.0001) + { + if (te > 0.9999) + { + dest->BezierTo (nx); + for (int i = ps; i <= pe; i++) + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + } + else + { + Geom::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe]); + PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+1]); + tx = (pnData->p + psData->p) / 2; + } + dest->BezierTo (tx); + for (int i = ps; i < pe; i++) + { + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + ReFormeBezierChunk (tx, nx, dest, inBezier, nbInterm, + from, pe, 0.0, te); + } + } + else + { + if (te > 0.9999) + { + Geom::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+1]); + PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+2]); + tx = (psData->p + pnData->p) / 2; + } + ReFormeBezierChunk (px, tx, dest, inBezier, nbInterm, + from, ps, ts, 1.0); + dest->BezierTo (nx); + for (int i = ps + 1; i <= pe; i++) + { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + } + else + { + Geom::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+1]); + PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+2]); + tx = (pnData->p + psData->p) / 2; + } + ReFormeBezierChunk (px, tx, dest, inBezier, nbInterm, + from, ps, ts, 1.0); + { + PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe]); + PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+1]); + tx = (pnData->p + psData->p) / 2; + } + dest->BezierTo (tx); + for (int i = ps + 1; i <= pe; i++) + { + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + ReFormeBezierChunk (tx, nx, dest, inBezier, nbInterm, + from, pe, 0.0, te); + } + } + } + else + { + if (ts > 0.9999) + { + if (te < 0.0001) + { + dest->BezierTo (nx); + for (int i = ps; i >= pe; i--) + { + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + } + else + { + Geom::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+1]); + PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+2]); + tx = (pnData->p + psData->p) / 2; + } + dest->BezierTo (tx); + for (int i = ps; i > pe; i--) + { + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + ReFormeBezierChunk (tx, nx, dest, inBezier, nbInterm, + from, pe, 1.0, te); + } + } + else + { + if (te < 0.0001) + { + Geom::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps]); + PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+1]); + tx = (pnData->p + psData->p) / 2; + } + ReFormeBezierChunk (px, tx, dest, inBezier, nbInterm, + from, ps, ts, 0.0); + dest->BezierTo (nx); + for (int i = ps + 1; i >= pe; i--) + { + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + } + else + { + Geom::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps]); + PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+1]); + tx = (pnData->p + psData->p) / 2; + } + ReFormeBezierChunk (px, tx, dest, inBezier, nbInterm, + from, ps, ts, 0.0); + { + PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+1]); + PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+2]); + tx = (pnData->p + psData->p) / 2; + } + dest->BezierTo (tx); + for (int i = ps + 1; i > pe; i--) + { + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + ReFormeBezierChunk (tx, nx, dest, inBezier, nbInterm, + from, pe, 1.0, te); + } + } + } + return bord; +} + +void +Shape::ReFormeBezierChunk (Geom::Point px, Geom::Point nx, + Path * dest, int inBezier, int nbInterm, + Path * from, int p, double ts, double te) +{ + PathDescrBezierTo* nBData = dynamic_cast<PathDescrBezierTo*>(from->descr_cmd[inBezier]); + Geom::Point bstx = from->PrevPoint (inBezier - 1); + Geom::Point benx = nBData->p; + + Geom::Point mx; + if (p == inBezier) + { + // premier bout + if (nbInterm <= 1) + { + // seul bout de la spline + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+1]); + mx = nData->p; + } + else + { + // premier bout d'une spline qui en contient plusieurs + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+1]); + mx = nData->p; + nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+2]); + benx = (nData->p + mx) / 2; + } + } + else if (p == inBezier + nbInterm - 1) + { + // dernier bout + // si nbInterm == 1, le cas a deja ete traite + // donc dernier bout d'une spline qui en contient plusieurs + PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+nbInterm]); + mx = nData->p; + nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+nbInterm-1]); + bstx = (nData->p + mx) / 2; + } + else + { + // la spline contient forcément plusieurs bouts, et ce n'est ni le premier ni le dernier + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[p+1]); + mx = nData->p; + nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[p]); + bstx = (nData->p + mx) / 2; + nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[p+2]); + benx = (nData->p + mx) / 2; + } + Geom::Point cx; + { + Path::QuadraticPoint ((ts + te) / 2, cx, bstx, mx, benx); + } + cx = 2 * cx - (px + nx) / 2; + { + dest->BezierTo (nx); + dest->IntermBezierTo (cx); + dest->EndBezierTo (); + } +} + +#undef MiscNormalize diff --git a/src/livarot/ShapeRaster.cpp b/src/livarot/ShapeRaster.cpp new file mode 100644 index 0000000..7aa300e --- /dev/null +++ b/src/livarot/ShapeRaster.cpp @@ -0,0 +1,2014 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* + * ShapeRaster.cpp + * nlivarot + * + * Created by fred on Sat Jul 19 2003. + * + */ + +#include "Shape.h" + +#include "livarot/float-line.h" +#include "AlphaLigne.h" +#include "BitLigne.h" + +#include "livarot/sweep-event-queue.h" +#include "livarot/sweep-tree-list.h" +#include "livarot/sweep-tree.h" + +/* + * polygon rasterization: the sweepline algorithm in all its glory + * nothing unusual in this implementation, so nothing special to say + * the *Quick*() functions are not useful. forget about them + */ + +void Shape::BeginRaster(float &pos, int &curPt) +{ + if ( numberOfPoints() <= 1 || numberOfEdges() <= 1 ) { + curPt = 0; + pos = 0; + return; + } + + MakeRasterData(true); + MakePointData(true); + MakeEdgeData(true); + + if (sTree == nullptr) { + sTree = new SweepTreeList(numberOfEdges()); + } + if (sEvts == nullptr) { + sEvts = new SweepEventQueue(numberOfEdges()); + } + + SortPoints(); + + curPt = 0; + pos = getPoint(0).x[1] - 1.0; + + for (int i = 0; i < numberOfPoints(); i++) { + pData[i].pending = 0; + pData[i].edgeOnLeft = -1; + pData[i].nextLinkedPoint = -1; + pData[i].rx[0] = /*Round(*/getPoint(i).x[0]/*)*/; + pData[i].rx[1] = /*Round(*/getPoint(i).x[1]/*)*/; + } + + for (int i = 0;i < numberOfEdges(); i++) { + swrData[i].misc = nullptr; + eData[i].rdx=pData[getEdge(i).en].rx - pData[getEdge(i).st].rx; + } +} + + +void Shape::EndRaster() +{ + delete sTree; + sTree = nullptr; + delete sEvts; + sEvts = nullptr; + + MakePointData(false); + MakeEdgeData(false); + MakeRasterData(false); +} + + +void Shape::BeginQuickRaster(float &pos, int &curPt) +{ + if ( numberOfPoints() <= 1 || numberOfEdges() <= 1 ) { + curPt = 0; + pos = 0; + return; + } + + MakeRasterData(true); + MakeQuickRasterData(true); + nbQRas = 0; + firstQRas = lastQRas = -1; + MakePointData(true); + MakeEdgeData(true); + + curPt = 0; + pos = getPoint(0).x[1] - 1.0; + + initialisePointData(); + + for (int i=0;i<numberOfEdges();i++) { + swrData[i].misc = nullptr; + qrsData[i].ind = -1; + eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx; + } + + SortPoints(); +// SortPointsRounded(); +} + + +void Shape::EndQuickRaster() +{ + MakePointData(false); + MakeEdgeData(false); + MakeRasterData(false); + MakeQuickRasterData(false); +} + + +// 2 versions of the Scan() series to move the scanline to a given position withou actually computing coverages +void Shape::Scan(float &pos, int &curP, float to, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + + if ( pos == to ) { + return; + } + + enum Direction { + DOWNWARDS, + UPWARDS + }; + + Direction const d = (pos < to) ? DOWNWARDS : UPWARDS; + + // points of the polygon are sorted top-down, so we take them in order, starting with the one at index curP, + // until we reach the wanted position to. + // don't forget to update curP and pos when we're done + int curPt = curP; + while ( ( d == DOWNWARDS && curPt < numberOfPoints() && getPoint(curPt).x[1] <= to) || + ( d == UPWARDS && curPt > 0 && getPoint(curPt - 1).x[1] >= to) ) + { + int nPt = (d == DOWNWARDS) ? curPt++ : --curPt; + + // treat a new point: remove and add edges incident to it + int nbUp; + int nbDn; + int upNo; + int dnNo; + _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo); + + if ( d == DOWNWARDS ) { + if ( nbDn <= 0 ) { + upNo = -1; + } + if ( upNo >= 0 && swrData[upNo].misc == nullptr ) { + upNo = -1; + } + } else { + if ( nbUp <= 0 ) { + dnNo = -1; + } + if ( dnNo >= 0 && swrData[dnNo].misc == nullptr ) { + dnNo = -1; + } + } + + if ( ( d == DOWNWARDS && nbUp > 0 ) || ( d == UPWARDS && nbDn > 0 ) ) { + // first remove edges coming from above or below, as appropriate + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + + Shape::dg_arete const &e = getEdge(cb); + if ( (d == DOWNWARDS && nPt == std::max(e.st, e.en)) || + (d == UPWARDS && nPt == std::min(e.st, e.en)) ) + { + if ( ( d == DOWNWARDS && cb != upNo ) || ( d == UPWARDS && cb != dnNo ) ) { + // we salvage the edge upNo to plug the edges we'll be addingat its place + // but the other edge don't have this chance + SweepTree *node = swrData[cb].misc; + if ( node ) { + swrData[cb].misc = nullptr; + node->Remove(*sTree, *sEvts, true); + } + } + } + cb = NextAt(nPt, cb); + } + } + + // if there is one edge going down and one edge coming from above, we don't Insert() the new edge, + // but replace the upNo edge by the new one (faster) + SweepTree* insertionNode = nullptr; + if ( dnNo >= 0 ) { + if ( upNo >= 0 ) { + int rmNo=(d == DOWNWARDS) ? upNo:dnNo; + int neNo=(d == DOWNWARDS) ? dnNo:upNo; + SweepTree* node = swrData[rmNo].misc; + swrData[rmNo].misc = nullptr; + + int const P = (d == DOWNWARDS) ? nPt : Other(nPt, neNo); + node->ConvertTo(this, neNo, 1, P); + + swrData[neNo].misc = node; + insertionNode = node; + CreateEdge(neNo, to, step); + } else { + // always DOWNWARDS + SweepTree* node = sTree->add(this, dnNo, 1, nPt, this); + swrData[dnNo].misc = node; + node->Insert(*sTree, *sEvts, this, nPt, true); + //if (d == UPWARDS) { + // node->startPoint = Other(nPt, dnNo); + //} + insertionNode = node; + CreateEdge(dnNo,to,step); + } + } else { + if ( upNo >= 0 ) { + // always UPWARDS + SweepTree* node = sTree->add(this, upNo, 1, nPt, this); + swrData[upNo].misc = node; + node->Insert(*sTree, *sEvts, this, nPt, true); + //if (d == UPWARDS) { + node->startPoint = Other(nPt, upNo); + //} + insertionNode = node; + CreateEdge(upNo,to,step); + } + } + + // add the remaining edges + if ( ( d == DOWNWARDS && nbDn > 1 ) || ( d == UPWARDS && nbUp > 1 ) ) { + // si nbDn == 1 , alors dnNo a deja ete traite + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::min(e.st, e.en) ) { + if ( cb != dnNo && cb != upNo ) { + SweepTree *node = sTree->add(this, cb, 1, nPt, this); + swrData[cb].misc = node; + node->InsertAt(*sTree, *sEvts, this, insertionNode, nPt, true); + if (d == UPWARDS) { + node->startPoint = Other(nPt, cb); + } + CreateEdge(cb, to, step); + } + } + cb = NextAt(nPt,cb); + } + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt - 1).x[1]; + } else { + pos = to; + } + + // the final touch: edges intersecting the sweepline must be update so that their intersection with + // said sweepline is correct. + pos = to; + if ( sTree->racine ) { + SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, true, step); + curS = static_cast<SweepTree*>(curS->elem[RIGHT]); + } + } +} + + + +void Shape::QuickScan(float &pos,int &curP, float to, bool /*doSort*/, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + + if ( pos == to ) { + return; + } + + enum Direction { + DOWNWARDS, + UPWARDS + }; + + Direction const d = (pos < to) ? DOWNWARDS : UPWARDS; + + int curPt = curP; + while ( (d == DOWNWARDS && curPt < numberOfPoints() && getPoint(curPt ).x[1] <= to) || + (d == UPWARDS && curPt > 0 && getPoint(curPt - 1).x[1] >= to) ) + { + int nPt = (d == DOWNWARDS) ? curPt++ : --curPt; + + int nbUp; + int nbDn; + int upNo; + int dnNo; + _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo); + + if ( nbDn <= 0 ) { + upNo = -1; + } + if ( upNo >= 0 && swrData[upNo].misc == nullptr ) { + upNo = -1; + } + + if ( nbUp > 0 ) { + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( (d == DOWNWARDS && nPt == std::max(e.st, e.en)) || + (d == UPWARDS && nPt == std::min(e.st, e.en)) ) + { + if ( cb != upNo ) { + QuickRasterSubEdge(cb); + } + } + cb = NextAt(nPt,cb); + } + } + + // traitement du "upNo devient dnNo" + int ins_guess = -1; + if ( dnNo >= 0 ) { + if ( upNo >= 0 ) { + ins_guess = QuickRasterChgEdge(upNo, dnNo, getPoint(nPt).x[0]); + } else { + ins_guess = QuickRasterAddEdge(dnNo, getPoint(nPt).x[0], ins_guess); + } + CreateEdge(dnNo, to, step); + } + + if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( (d == DOWNWARDS && nPt == std::min(e.st, e.en)) || + (d == UPWARDS && nPt == std::max(e.st, e.en)) ) + { + if ( cb != dnNo ) { + ins_guess = QuickRasterAddEdge(cb, getPoint(nPt).x[0], ins_guess); + CreateEdge(cb, to, step); + } + } + cb = NextAt(nPt,cb); + } + } + + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt-1).x[1]; + } else { + pos = to; + } + } + + pos = to; + + for (int i=0; i < nbQRas; i++) { + int cb = qrsData[i].bord; + AvanceEdge(cb, to, true, step); + qrsData[i].x=swrData[cb].curX; + } + + QuickRasterSort(); +} + + + +int Shape::QuickRasterChgEdge(int oBord, int nBord, double x) +{ + if ( oBord == nBord ) { + return -1; + } + + int no = qrsData[oBord].ind; + if ( no >= 0 ) { + qrsData[no].bord = nBord; + qrsData[no].x = x; + qrsData[oBord].ind = -1; + qrsData[nBord].ind = no; + } + + return no; +} + + + +int Shape::QuickRasterAddEdge(int bord, double x, int guess) +{ + int no = nbQRas++; + qrsData[no].bord = bord; + qrsData[no].x = x; + qrsData[bord].ind = no; + qrsData[no].prev = -1; + qrsData[no].next = -1; + + if ( no < 0 || no >= nbQRas ) { + return -1; + } + + if ( firstQRas < 0 ) { + firstQRas = lastQRas = no; + qrsData[no].prev = -1; + qrsData[no].next = -1; + return no; + } + + if ( guess < 0 || guess >= nbQRas ) { + + int c = firstQRas; + while ( c >= 0 && c < nbQRas && CmpQRs(qrsData[c],qrsData[no]) < 0 ) { + c = qrsData[c].next; + } + + if ( c < 0 || c >= nbQRas ) { + qrsData[no].prev = lastQRas; + qrsData[lastQRas].next = no; + lastQRas = no; + } else { + qrsData[no].prev = qrsData[c].prev; + if ( qrsData[no].prev >= 0 ) { + qrsData[qrsData[no].prev].next=no; + } else { + firstQRas = no; + } + + qrsData[no].next = c; + qrsData[c].prev = no; + } + + } else { + int c = guess; + int stTst = CmpQRs(qrsData[c],qrsData[no]); + if ( stTst == 0 ) { + + qrsData[no].prev = qrsData[c].prev; + if ( qrsData[no].prev >= 0 ) { + qrsData[qrsData[no].prev].next = no; + } else { + firstQRas = no; + } + + qrsData[no].next = c; + qrsData[c].prev = no; + + } else if ( stTst > 0 ) { + + while ( c >= 0 && c < nbQRas && CmpQRs(qrsData[c],qrsData[no]) > 0 ) { + c = qrsData[c].prev; + } + + if ( c < 0 || c >= nbQRas ) { + qrsData[no].next = firstQRas; + qrsData[firstQRas].prev = no; // firstQRas != -1 + firstQRas = no; + } else { + qrsData[no].next = qrsData[c].next; + if ( qrsData[no].next >= 0 ) { + qrsData[qrsData[no].next].prev = no; + } else { + lastQRas = no; + } + qrsData[no].prev = c; + qrsData[c].next = no; + } + + } else { + + while ( c >= 0 && c < nbQRas && CmpQRs(qrsData[c],qrsData[no]) < 0 ) { + c = qrsData[c].next; + } + + if ( c < 0 || c >= nbQRas ) { + qrsData[no].prev = lastQRas; + qrsData[lastQRas].next = no; + lastQRas = no; + } else { + qrsData[no].prev = qrsData[c].prev; + if ( qrsData[no].prev >= 0 ) { + qrsData[qrsData[no].prev].next = no; + } else { + firstQRas = no; + } + + qrsData[no].next = c; + qrsData[c].prev = no; + } + } + } + + return no; +} + + + +void Shape::QuickRasterSubEdge(int bord) +{ + int no = qrsData[bord].ind; + if ( no < 0 || no >= nbQRas ) { + return; // euuhHHH + } + + if ( qrsData[no].prev >= 0 ) { + qrsData[qrsData[no].prev].next=qrsData[no].next; + } + + if ( qrsData[no].next >= 0 ) { + qrsData[qrsData[no].next].prev = qrsData[no].prev; + } + + if ( no == firstQRas ) { + firstQRas = qrsData[no].next; + } + + if ( no == lastQRas ) { + lastQRas = qrsData[no].prev; + } + + qrsData[no].prev = qrsData[no].next = -1; + + int savInd = qrsData[no].ind; + qrsData[no] = qrsData[--nbQRas]; + qrsData[no].ind = savInd; + qrsData[qrsData[no].bord].ind = no; + qrsData[bord].ind = -1; + + if ( nbQRas > 0 ) { + if ( firstQRas == nbQRas ) { + firstQRas = no; + } + if ( lastQRas == nbQRas ) { + lastQRas = no; + } + if ( qrsData[no].prev >= 0 ) { + qrsData[qrsData[no].prev].next = no; + } + if ( qrsData[no].next >= 0 ) { + qrsData[qrsData[no].next].prev = no; + } + } +} + + + +void Shape::QuickRasterSwapEdge(int a, int b) +{ + if ( a == b ) { + return; + } + + int na = qrsData[a].ind; + int nb = qrsData[b].ind; + if ( na < 0 || na >= nbQRas || nb < 0 || nb >= nbQRas ) { + return; // errrm + } + + qrsData[na].bord = b; + qrsData[nb].bord = a; + qrsData[a].ind = nb; + qrsData[b].ind = na; + + double swd = qrsData[na].x; + qrsData[na].x = qrsData[nb].x; + qrsData[nb].x = swd; +} + + +void Shape::QuickRasterSort() +{ + if ( nbQRas <= 1 ) { + return; + } + + int cb = qrsData[firstQRas].bord; + + while ( cb >= 0 ) { + int bI = qrsData[cb].ind; + int nI = qrsData[bI].next; + + if ( nI < 0 ) { + break; + } + + int ncb = qrsData[nI].bord; + if ( CmpQRs(qrsData[nI], qrsData[bI]) < 0 ) { + QuickRasterSwapEdge(cb, ncb); + int pI = qrsData[bI].prev; // ca reste bI, puisqu'on a juste echange les contenus + if ( pI < 0 ) { + cb = ncb; // en fait inutile; mais bon... + } else { + int pcb = qrsData[pI].bord; + cb = pcb; + } + } else { + cb = ncb; + } + } +} + + +// direct scan to a given position. goes through the edge list to keep only the ones intersecting the target sweepline +// good for initial setup of scanline algo, bad for incremental changes +void Shape::DirectScan(float &pos, int &curP, float to, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + + if ( pos == to ) { + return; + } + + if ( pos < to ) { + // we're moving downwards + // points of the polygon are sorted top-down, so we take them in order, starting with the one at index curP, + // until we reach the wanted position to. + // don't forget to update curP and pos when we're done + int curPt = curP; + while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) { + curPt++; + } + + for (int i=0;i<numberOfEdges();i++) { + if ( swrData[i].misc ) { + SweepTree* node = swrData[i].misc; + swrData[i].misc = nullptr; + node->Remove(*sTree, *sEvts, true); + } + } + + for (int i=0; i < numberOfEdges(); i++) { + Shape::dg_arete const &e = getEdge(i); + if ( ( e.st < curPt && e.en >= curPt ) || ( e.en < curPt && e.st >= curPt )) { + // crosses sweepline + int nPt = (e.st < curPt) ? e.st : e.en; + SweepTree* node = sTree->add(this, i, 1, nPt, this); + swrData[i].misc = node; + node->Insert(*sTree, *sEvts, this, nPt, true); + CreateEdge(i, to, step); + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt - 1).x[1]; + } else { + pos = to; + } + + } else { + + // same thing, but going up. so the sweepSens is inverted for the Find() function + int curPt=curP; + while ( curPt > 0 && getPoint(curPt-1).x[1] >= to ) { + curPt--; + } + + for (int i = 0; i < numberOfEdges(); i++) { + if ( swrData[i].misc ) { + SweepTree* node = swrData[i].misc; + swrData[i].misc = nullptr; + node->Remove(*sTree, *sEvts, true); + } + } + + for (int i=0;i<numberOfEdges();i++) { + Shape::dg_arete const &e = getEdge(i); + if ( ( e.st > curPt - 1 && e.en <= curPt - 1 ) || ( e.en > curPt - 1 && e.st <= curPt - 1 )) { + // crosses sweepline + int nPt = (e.st > curPt) ? e.st : e.en; + SweepTree* node = sTree->add(this, i, 1, nPt, this); + swrData[i].misc = node; + node->Insert(*sTree, *sEvts, this, nPt, false); + node->startPoint = Other(nPt, i); + CreateEdge(i, to, step); + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt - 1).x[1]; + } else { + pos = to; + } + } + + // the final touch: edges intersecting the sweepline must be update so that their intersection with + // said sweepline is correct. + pos = to; + if ( sTree->racine ) { + SweepTree* curS=static_cast<SweepTree*>(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, true, step); + curS = static_cast<SweepTree*>(curS->elem[RIGHT]); + } + } +} + + + +void Shape::DirectQuickScan(float &pos, int &curP, float to, bool /*doSort*/, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + + if ( pos == to ) { + return; + } + + if ( pos < to ) { + // we're moving downwards + // points of the polygon are sorted top-down, so we take them in order, starting with the one at index curP, + // until we reach the wanted position to. + // don't forget to update curP and pos when we're done + int curPt=curP; + while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) { + curPt++; + } + + for (int i = 0; i < numberOfEdges(); i++) { + if ( qrsData[i].ind < 0 ) { + QuickRasterSubEdge(i); + } + } + + for (int i = 0; i < numberOfEdges(); i++) { + Shape::dg_arete const &e = getEdge(i); + if ( ( e.st < curPt && e.en >= curPt ) || ( e.en < curPt && e.st >= curPt )) { + // crosses sweepline + int nPt = (e.st < e.en) ? e.st : e.en; + QuickRasterAddEdge(i, getPoint(nPt).x[0], -1); + CreateEdge(i, to, step); + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos=getPoint(curPt-1).x[1]; + } else { + pos = to; + } + + } else { + + // same thing, but going up. so the sweepSens is inverted for the Find() function + int curPt=curP; + while ( curPt > 0 && getPoint(curPt-1).x[1] >= to ) { + curPt--; + } + + for (int i = 0; i < numberOfEdges(); i++) { + if ( qrsData[i].ind < 0 ) { + QuickRasterSubEdge(i); + } + } + + for (int i=0;i<numberOfEdges();i++) { + Shape::dg_arete const &e = getEdge(i); + if ( ( e.st < curPt-1 && e.en >= curPt-1 ) || ( e.en < curPt-1 && e.st >= curPt-1 )) { + // crosses sweepline + int nPt = (e.st > e.en) ? e.st : e.en; + QuickRasterAddEdge(i, getPoint(nPt).x[0], -1); + CreateEdge(i, to, step); + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt-1).x[1]; + } else { + pos = to; + } + + } + + pos = to; + for (int i = 0; i < nbQRas; i++) { + int cb = qrsData[i].bord; + AvanceEdge(cb, to, true, step); + qrsData[i].x = swrData[cb].curX; + } + + QuickRasterSort(); +} + + +// scan and compute coverage, FloatLigne version coverage of the line is bult in 2 parts: first a +// set of rectangles of height the height of the line (here: "step") one rectangle for each portion +// of the sweepline that is in the polygon at the beginning of the scan. then a set ot trapezoids +// are added or removed to these rectangles, one trapezoid for each edge destroyed or edge crossing +// the entire line. think of it as a refinement of the coverage by rectangles + +void Shape::Scan(float &pos, int &curP, float to, FloatLigne *line, bool exact, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + + if ( pos >= to ) { + return; + } + + // first step: the rectangles since we read the sweepline left to right, we know the + // boundaries of the rectangles are appended in a list, hence the AppendBord(). we salvage + // the guess value for the trapezoids the edges will induce + + if ( sTree->racine ) { + SweepTree *curS = static_cast<SweepTree*>(sTree->racine->Leftmost()); + while ( curS ) { + + int lastGuess = -1; + int cb = curS->bord; + + if ( swrData[cb].sens == false && curS->elem[LEFT] ) { + + int lb = (static_cast<SweepTree*>(curS->elem[LEFT]))->bord; + + lastGuess = line->AppendBord(swrData[lb].curX, + to - swrData[lb].curY, + swrData[cb].curX, + to - swrData[cb].curY,0.0); + + swrData[lb].guess = lastGuess - 1; + swrData[cb].guess = lastGuess; + } else { + int lb = curS->bord; + swrData[lb].guess = -1; + } + + curS=static_cast <SweepTree*> (curS->elem[RIGHT]); + } + } + + int curPt = curP; + while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) { + + int nPt = curPt++; + + // same thing as the usual Scan(), just with a hardcoded "indegree+outdegree=2" case, since + // it's the most common one + + int nbUp; + int nbDn; + int upNo; + int dnNo; + if ( getPoint(nPt).totalDegree() == 2 ) { + _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } else { + _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } + + if ( nbDn <= 0 ) { + upNo = -1; + } + if ( upNo >= 0 && swrData[upNo].misc == nullptr ) { + upNo = -1; + } + + if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) { + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::max(e.st, e.en) ) { + if ( cb != upNo ) { + SweepTree* node = swrData[cb].misc; + if ( node ) { + _updateIntersection(cb, nPt); + // create trapezoid for the chunk of edge intersecting with the line + DestroyEdge(cb, to, line); + node->Remove(*sTree, *sEvts, true); + } + } + } + + cb = NextAt(nPt,cb); + } + } + + // traitement du "upNo devient dnNo" + SweepTree *insertionNode = nullptr; + if ( dnNo >= 0 ) { + if ( upNo >= 0 ) { + SweepTree* node = swrData[upNo].misc; + _updateIntersection(upNo, nPt); + DestroyEdge(upNo, to, line); + + node->ConvertTo(this, dnNo, 1, nPt); + + swrData[dnNo].misc = node; + insertionNode = node; + CreateEdge(dnNo, to, step); + swrData[dnNo].guess = swrData[upNo].guess; + } else { + SweepTree *node = sTree->add(this, dnNo, 1, nPt, this); + swrData[dnNo].misc = node; + node->Insert(*sTree, *sEvts, this, nPt, true); + insertionNode = node; + CreateEdge(dnNo, to, step); + } + } + + if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::min(e.st, e.en) ) { + if ( cb != dnNo ) { + SweepTree *node = sTree->add(this, cb, 1, nPt, this); + swrData[cb].misc = node; + node->InsertAt(*sTree, *sEvts, this, insertionNode, nPt, true); + CreateEdge(cb, to, step); + } + } + cb = NextAt(nPt,cb); + } + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt - 1).x[1]; + } else { + pos = to; + } + + // update intersections with the sweepline, and add trapezoids for edges crossing the line + pos = to; + if ( sTree->racine ) { + SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, line, exact, step); + curS = static_cast<SweepTree*>(curS->elem[RIGHT]); + } + } +} + + + + +void Shape::Scan(float &pos, int &curP, float to, FillRule directed, BitLigne *line, bool exact, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + + if ( pos >= to ) { + return; + } + + if ( sTree->racine ) { + int curW = 0; + float lastX = 0; + SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost()); + + if ( directed == fill_oddEven ) { + + while ( curS ) { + int cb = curS->bord; + curW++; + curW &= 0x00000001; + if ( curW == 0 ) { + line->AddBord(lastX,swrData[cb].curX,true); + } else { + lastX = swrData[cb].curX; + } + curS = static_cast<SweepTree*>(curS->elem[RIGHT]); + } + + } else if ( directed == fill_positive ) { + + // doesn't behave correctly; no way i know to do this without a ConvertToShape() + while ( curS ) { + int cb = curS->bord; + int oW = curW; + if ( swrData[cb].sens ) { + curW++; + } else { + curW--; + } + + if ( curW <= 0 && oW > 0) { + line->AddBord(lastX, swrData[cb].curX, true); + } else if ( curW > 0 && oW <= 0 ) { + lastX = swrData[cb].curX; + } + + curS = static_cast<SweepTree*>(curS->elem[RIGHT]); + } + + } else if ( directed == fill_nonZero ) { + + while ( curS ) { + int cb = curS->bord; + int oW = curW; + if ( swrData[cb].sens ) { + curW++; + } else { + curW--; + } + + if ( curW == 0 && oW != 0) { + line->AddBord(lastX,swrData[cb].curX,true); + } else if ( curW != 0 && oW == 0 ) { + lastX=swrData[cb].curX; + } + curS = static_cast<SweepTree*>(curS->elem[RIGHT]); + } + } + + } + + int curPt = curP; + while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) { + int nPt = curPt++; + + int cb; + int nbUp; + int nbDn; + int upNo; + int dnNo; + + if ( getPoint(nPt).totalDegree() == 2 ) { + _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } else { + _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } + + if ( nbDn <= 0 ) { + upNo = -1; + } + if ( upNo >= 0 && swrData[upNo].misc == nullptr ) { + upNo = -1; + } + + if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) { + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::max(e.st, e.en) ) { + if ( cb != upNo ) { + SweepTree* node=swrData[cb].misc; + if ( node ) { + _updateIntersection(cb, nPt); + DestroyEdge(cb, line); + node->Remove(*sTree,*sEvts,true); + } + } + } + cb = NextAt(nPt,cb); + } + } + + // traitement du "upNo devient dnNo" + SweepTree* insertionNode = nullptr; + if ( dnNo >= 0 ) { + if ( upNo >= 0 ) { + SweepTree* node = swrData[upNo].misc; + _updateIntersection(upNo, nPt); + DestroyEdge(upNo, line); + + node->ConvertTo(this, dnNo, 1, nPt); + + swrData[dnNo].misc = node; + insertionNode = node; + CreateEdge(dnNo, to, step); + + } else { + + SweepTree* node = sTree->add(this,dnNo,1,nPt,this); + swrData[dnNo].misc = node; + node->Insert(*sTree, *sEvts, this, nPt, true); + insertionNode = node; + CreateEdge(dnNo, to, step); + } + } + + if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite + cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::min(e.st, e.en) ) { + if ( cb != dnNo ) { + SweepTree* node = sTree->add(this, cb, 1, nPt, this); + swrData[cb].misc = node; + node->InsertAt(*sTree, *sEvts, this, insertionNode, nPt, true); + CreateEdge(cb, to, step); + } + } + cb = NextAt(nPt, cb); + } + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt - 1).x[1]; + } else { + pos = to; + } + + pos = to; + if ( sTree->racine ) { + SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, line, exact, step); + curS = static_cast<SweepTree*>(curS->elem[RIGHT]); + } + } +} + + +void Shape::Scan(float &pos, int &curP, float to, AlphaLigne *line, bool exact, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + + if ( pos >= to ) { + return; + } + + int curPt = curP; + while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) { + int nPt = curPt++; + + int nbUp; + int nbDn; + int upNo; + int dnNo; + if ( getPoint(nPt).totalDegree() == 2 ) { + _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } else { + _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } + + if ( nbDn <= 0 ) { + upNo=-1; + } + if ( upNo >= 0 && swrData[upNo].misc == nullptr ) { + upNo=-1; + } + + if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) { + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::max(e.st, e.en) ) { + if ( cb != upNo ) { + SweepTree* node = swrData[cb].misc; + if ( node ) { + _updateIntersection(cb, nPt); + DestroyEdge(cb, line); + node->Remove(*sTree, *sEvts, true); + } + } + } + + cb = NextAt(nPt,cb); + } + } + + // traitement du "upNo devient dnNo" + SweepTree* insertionNode = nullptr; + if ( dnNo >= 0 ) { + if ( upNo >= 0 ) { + SweepTree* node = swrData[upNo].misc; + _updateIntersection(upNo, nPt); + DestroyEdge(upNo, line); + + node->ConvertTo(this, dnNo, 1, nPt); + + swrData[dnNo].misc = node; + insertionNode = node; + CreateEdge(dnNo, to, step); + swrData[dnNo].guess = swrData[upNo].guess; + } else { + SweepTree* node = sTree->add(this, dnNo, 1, nPt, this); + swrData[dnNo].misc = node; + node->Insert(*sTree, *sEvts, this, nPt, true); + insertionNode = node; + CreateEdge(dnNo, to, step); + } + } + + if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::min(e.st, e.en) ) { + if ( cb != dnNo ) { + SweepTree* node = sTree->add(this, cb, 1, nPt, this); + swrData[cb].misc = node; + node->InsertAt(*sTree, *sEvts, this, insertionNode, nPt, true); + CreateEdge(cb, to, step); + } + } + cb = NextAt(nPt,cb); + } + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt - 1).x[1]; + } else { + pos = to; + } + + pos = to; + if ( sTree->racine ) { + SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, line, exact, step); + curS = static_cast<SweepTree*>(curS->elem[RIGHT]); + } + } +} + + + +void Shape::QuickScan(float &pos, int &curP, float to, FloatLigne* line, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + + if ( pos >= to ) { + return; + } + + if ( nbQRas > 1 ) { + int curW = 0; + // float lastX = 0; + // float lastY = 0; + int lastGuess = -1; + int lastB = -1; + + for (int i = firstQRas; i >= 0 && i < nbQRas; i = qrsData[i].next) { + int cb = qrsData[i].bord; + int oW = curW; + if ( swrData[cb].sens ) { + curW++; + } else { + curW--; + } + + if ( curW % 2 == 0 && oW % 2 != 0) { + + lastGuess = line->AppendBord(swrData[lastB].curX, + to - swrData[lastB].curY, + swrData[cb].curX, + to - swrData[cb].curY, + 0.0); + + swrData[cb].guess = lastGuess; + if ( lastB >= 0 ) { + swrData[lastB].guess = lastGuess - 1; + } + + } else if ( curW%2 != 0 && oW%2 == 0 ) { + + // lastX = swrData[cb].curX; + // lastY = swrData[cb].curY; + lastB = cb; + swrData[cb].guess = -1; + + } else { + swrData[cb].guess = -1; + } + } + } + + int curPt = curP; + while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) { + int nPt = curPt++; + + int nbUp; + int nbDn; + int upNo; + int dnNo; + if ( getPoint(nPt).totalDegree() == 2 ) { + _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } else { + _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } + + if ( nbDn <= 0 ) { + upNo = -1; + } + if ( upNo >= 0 && swrData[upNo].misc == nullptr ) { + upNo = -1; + } + + if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) { + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::max(e.st, e.en) ) { + if ( cb != upNo ) { + QuickRasterSubEdge(cb); + _updateIntersection(cb, nPt); + DestroyEdge(cb, to, line); + } + } + cb = NextAt(nPt, cb); + } + } + + // traitement du "upNo devient dnNo" + int ins_guess=-1; + if ( dnNo >= 0 ) { + if ( upNo >= 0 ) { + ins_guess = QuickRasterChgEdge(upNo ,dnNo, getPoint(nPt).x[0]); + _updateIntersection(upNo, nPt); + DestroyEdge(upNo, to, line); + + CreateEdge(dnNo, to, step); + swrData[dnNo].guess = swrData[upNo].guess; + } else { + ins_guess = QuickRasterAddEdge(dnNo, getPoint(nPt).x[0], ins_guess); + CreateEdge(dnNo, to, step); + } + } + + if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::min(e.st, e.en) ) { + if ( cb != dnNo ) { + ins_guess = QuickRasterAddEdge(cb, getPoint(nPt).x[0], ins_guess); + CreateEdge(cb, to, step); + } + } + cb = NextAt(nPt, cb); + } + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt-1).x[1]; + } else { + pos=to; + } + + pos = to; + for (int i=0; i < nbQRas; i++) { + int cb = qrsData[i].bord; + AvanceEdge(cb, to, line, true, step); + qrsData[i].x = swrData[cb].curX; + } + + QuickRasterSort(); +} + + + + +void Shape::QuickScan(float &pos, int &curP, float to, FillRule directed, BitLigne* line, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + + if ( pos >= to ) { + return; + } + + if ( nbQRas > 1 ) { + int curW = 0; + float lastX = 0; + + if ( directed == fill_oddEven ) { + + for (int i = firstQRas; i >= 0 && i < nbQRas; i = qrsData[i].next) { + int cb = qrsData[i].bord; + curW++; + curW &= 1; + if ( curW == 0 ) { + line->AddBord(lastX, swrData[cb].curX, true); + } else { + lastX = swrData[cb].curX; + } + } + + } else if ( directed == fill_positive ) { + // doesn't behave correctly; no way i know to do this without a ConvertToShape() + for (int i = firstQRas; i >= 0 && i < nbQRas; i = qrsData[i].next) { + int cb = qrsData[i].bord; + int oW = curW; + if ( swrData[cb].sens ) { + curW++; + } else { + curW--; + } + + if ( curW <= 0 && oW > 0) { + line->AddBord(lastX, swrData[cb].curX, true); + } else if ( curW > 0 && oW <= 0 ) { + lastX = swrData[cb].curX; + } + } + + } else if ( directed == fill_nonZero ) { + for (int i = firstQRas; i >= 0 && i < nbQRas; i = qrsData[i].next) { + int cb = qrsData[i].bord; + int oW = curW; + if ( swrData[cb].sens ) { + curW++; + } else { + curW--; + } + + if ( curW == 0 && oW != 0) { + line->AddBord(lastX, swrData[cb].curX, true); + } else if ( curW != 0 && oW == 0 ) { + lastX = swrData[cb].curX; + } + } + } + } + + int curPt = curP; + while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) { + int nPt = curPt++; + + int nbUp; + int nbDn; + int upNo; + int dnNo; + if ( getPoint(nPt).totalDegree() == 2 ) { + _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } else { + _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } + + if ( nbDn <= 0 ) { + upNo = -1; + } + + if ( upNo >= 0 && swrData[upNo].misc == nullptr ) { + upNo = -1; + } + + if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) { + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::max(e.st, e.en) ) { + if ( cb != upNo ) { + QuickRasterSubEdge(cb); + _updateIntersection(cb, nPt); + DestroyEdge(cb, line); + } + } + cb = NextAt(nPt, cb); + } + } + + // traitement du "upNo devient dnNo" + int ins_guess = -1; + if ( dnNo >= 0 ) { + if ( upNo >= 0 ) { + ins_guess = QuickRasterChgEdge(upNo, dnNo, getPoint(nPt).x[0]); + _updateIntersection(upNo, nPt); + DestroyEdge(upNo, line); + + CreateEdge(dnNo, to, step); + } else { + ins_guess = QuickRasterAddEdge(dnNo, getPoint(nPt).x[0], ins_guess); + CreateEdge(dnNo, to, step); + } + } + + if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::min(e.st, e.en) ) { + if ( cb != dnNo ) { + ins_guess = QuickRasterAddEdge(cb, getPoint(nPt).x[0], ins_guess); + CreateEdge(cb, to, step); + } + } + cb = NextAt(nPt,cb); + } + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos=getPoint(curPt - 1).x[1]; + } else { + pos = to; + } + + pos = to; + for (int i = 0; i < nbQRas; i++) { + int cb = qrsData[i].bord; + AvanceEdge(cb, to, line, true, step); + qrsData[i].x = swrData[cb].curX; + } + + QuickRasterSort(); +} + + + +void Shape::QuickScan(float &pos, int &curP, float to, AlphaLigne* line, float step) +{ + if ( numberOfEdges() <= 1 ) { + return; + } + if ( pos >= to ) { + return; + } + + int curPt = curP; + while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) { + int nPt = curPt++; + + int nbUp; + int nbDn; + int upNo; + int dnNo; + if ( getPoint(nPt).totalDegree() == 2 ) { + _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } else { + _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo); + } + + if ( nbDn <= 0 ) { + upNo = -1; + } + if ( upNo >= 0 && swrData[upNo].misc == nullptr ) { + upNo = -1; + } + + if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) { + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::max(e.st, e.en) ) { + if ( cb != upNo ) { + QuickRasterSubEdge(cb); + _updateIntersection(cb, nPt); + DestroyEdge(cb, line); + } + } + cb = NextAt(nPt,cb); + } + } + + // traitement du "upNo devient dnNo" + int ins_guess = -1; + if ( dnNo >= 0 ) { + if ( upNo >= 0 ) { + ins_guess = QuickRasterChgEdge(upNo, dnNo, getPoint(nPt).x[0]); + _updateIntersection(upNo, nPt); + DestroyEdge(upNo, line); + + CreateEdge(dnNo, to, step); + swrData[dnNo].guess = swrData[upNo].guess; + } else { + ins_guess = QuickRasterAddEdge(dnNo, getPoint(nPt).x[0], ins_guess); + CreateEdge(dnNo, to, step); + } + } + + if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite + int cb = getPoint(nPt).incidentEdge[FIRST]; + while ( cb >= 0 && cb < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(cb); + if ( nPt == std::min(e.st, e.en) ) { + if ( cb != dnNo ) { + ins_guess = QuickRasterAddEdge(cb,getPoint(nPt).x[0], ins_guess); + CreateEdge(cb, to, step); + } + } + cb = NextAt(nPt,cb); + } + } + } + + curP = curPt; + if ( curPt > 0 ) { + pos = getPoint(curPt-1).x[1]; + } else { + pos = to; + } + + pos = to; + for (int i = 0; i < nbQRas; i++) { + int cb = qrsData[i].bord; + AvanceEdge(cb, to, line, true, step); + qrsData[i].x = swrData[cb].curX; + } + + QuickRasterSort(); +} + + +/* + * operations de bases pour la rasterization + * + */ +void Shape::CreateEdge(int no, float to, float step) +{ + int cPt; + Geom::Point dir; + if ( getEdge(no).st < getEdge(no).en ) { + cPt = getEdge(no).st; + swrData[no].sens = true; + dir = getEdge(no).dx; + } else { + cPt = getEdge(no).en; + swrData[no].sens = false; + dir = -getEdge(no).dx; + } + + swrData[no].lastX = swrData[no].curX = getPoint(cPt).x[0]; + swrData[no].lastY = swrData[no].curY = getPoint(cPt).x[1]; + + if ( fabs(dir[1]) < 0.000001 ) { + swrData[no].dxdy = 0; + } else { + swrData[no].dxdy = dir[0]/dir[1]; + } + + if ( fabs(dir[0]) < 0.000001 ) { + swrData[no].dydx = 0; + } else { + swrData[no].dydx = dir[1]/dir[0]; + } + + swrData[no].calcX = swrData[no].curX + (to - step - swrData[no].curY) * swrData[no].dxdy; + swrData[no].guess = -1; +} + + +void Shape::AvanceEdge(int no, float to, bool exact, float step) +{ + if ( exact ) { + Geom::Point dir; + Geom::Point stp; + if ( swrData[no].sens ) { + stp = getPoint(getEdge(no).st).x; + dir = getEdge(no).dx; + } else { + stp = getPoint(getEdge(no).en).x; + dir = -getEdge(no).dx; + } + + if ( fabs(dir[1]) < 0.000001 ) { + swrData[no].calcX = stp[0] + dir[0]; + } else { + swrData[no].calcX = stp[0] + ((to - stp[1]) * dir[0]) / dir[1]; + } + } else { + swrData[no].calcX += step * swrData[no].dxdy; + } + + swrData[no].lastX = swrData[no].curX; + swrData[no].lastY = swrData[no].curY; + swrData[no].curX = swrData[no].calcX; + swrData[no].curY = to; +} + +/* + * specialisation par type de structure utilise + */ + +void Shape::DestroyEdge(int no, float to, FloatLigne* line) +{ + if ( swrData[no].sens ) { + + if ( swrData[no].curX < swrData[no].lastX ) { + + swrData[no].guess = line->AddBordR(swrData[no].curX, + to - swrData[no].curY, + swrData[no].lastX, + to - swrData[no].lastY, + -swrData[no].dydx, + swrData[no].guess); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + swrData[no].guess = line->AddBord(swrData[no].lastX, + -(to - swrData[no].lastY), + swrData[no].curX, + -(to - swrData[no].curY), + swrData[no].dydx, + swrData[no].guess); + } + + } else { + + if ( swrData[no].curX < swrData[no].lastX ) { + + swrData[no].guess = line->AddBordR(swrData[no].curX, + -(to - swrData[no].curY), + swrData[no].lastX, + -(to - swrData[no].lastY), + swrData[no].dydx, + swrData[no].guess); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + swrData[no].guess = line->AddBord(swrData[no].lastX, + to - swrData[no].lastY, + swrData[no].curX, + to - swrData[no].curY, + -swrData[no].dydx, + swrData[no].guess); + } + } +} + + + +void Shape::AvanceEdge(int no, float to, FloatLigne *line, bool exact, float step) +{ + AvanceEdge(no,to,exact,step); + + if ( swrData[no].sens ) { + + if ( swrData[no].curX < swrData[no].lastX ) { + + swrData[no].guess = line->AddBordR(swrData[no].curX, + to - swrData[no].curY, + swrData[no].lastX, + to - swrData[no].lastY, + -swrData[no].dydx, + swrData[no].guess); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + swrData[no].guess = line->AddBord(swrData[no].lastX, + -(to - swrData[no].lastY), + swrData[no].curX, + -(to - swrData[no].curY), + swrData[no].dydx, + swrData[no].guess); + } + + } else { + + if ( swrData[no].curX < swrData[no].lastX ) { + + swrData[no].guess = line->AddBordR(swrData[no].curX, + -(to - swrData[no].curY), + swrData[no].lastX, + -(to - swrData[no].lastY), + swrData[no].dydx, + swrData[no].guess); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + swrData[no].guess = line->AddBord(swrData[no].lastX, + to - swrData[no].lastY, + swrData[no].curX, + to - swrData[no].curY, + -swrData[no].dydx, + swrData[no].guess); + } + } +} + + +void Shape::DestroyEdge(int no, BitLigne *line) +{ + if ( swrData[no].sens ) { + + if ( swrData[no].curX < swrData[no].lastX ) { + + line->AddBord(swrData[no].curX, swrData[no].lastX, false); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + line->AddBord(swrData[no].lastX,swrData[no].curX,false); + } + + } else { + + if ( swrData[no].curX < swrData[no].lastX ) { + + line->AddBord(swrData[no].curX, swrData[no].lastX, false); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + line->AddBord(swrData[no].lastX, swrData[no].curX, false); + + } + } +} + + +void Shape::AvanceEdge(int no, float to, BitLigne *line, bool exact, float step) +{ + AvanceEdge(no, to, exact, step); + + if ( swrData[no].sens ) { + + if ( swrData[no].curX < swrData[no].lastX ) { + + line->AddBord(swrData[no].curX, swrData[no].lastX, false); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + line->AddBord(swrData[no].lastX, swrData[no].curX, false); + } + + } else { + + if ( swrData[no].curX < swrData[no].lastX ) { + + line->AddBord(swrData[no].curX, swrData[no].lastX, false); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + line->AddBord(swrData[no].lastX, swrData[no].curX, false); + } + } +} + + +void Shape::DestroyEdge(int no, AlphaLigne* line) +{ + if ( swrData[no].sens ) { + + if ( swrData[no].curX <= swrData[no].lastX ) { + + line->AddBord(swrData[no].curX, + 0, + swrData[no].lastX, + swrData[no].curY - swrData[no].lastY, + -swrData[no].dydx); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + line->AddBord(swrData[no].lastX, + 0, + swrData[no].curX, + swrData[no].curY - swrData[no].lastY, + swrData[no].dydx); + } + + } else { + + if ( swrData[no].curX <= swrData[no].lastX ) { + + line->AddBord(swrData[no].curX, + 0, + swrData[no].lastX, + swrData[no].lastY - swrData[no].curY, + swrData[no].dydx); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + line->AddBord(swrData[no].lastX, + 0, + swrData[no].curX, + swrData[no].lastY - swrData[no].curY, + -swrData[no].dydx); + } + } +} + + +void Shape::AvanceEdge(int no, float to, AlphaLigne *line, bool exact, float step) +{ + AvanceEdge(no,to,exact,step); + + if ( swrData[no].sens ) { + + if ( swrData[no].curX <= swrData[no].lastX ) { + + line->AddBord(swrData[no].curX, + 0, + swrData[no].lastX, + swrData[no].curY - swrData[no].lastY, + -swrData[no].dydx); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + line->AddBord(swrData[no].lastX, + 0, + swrData[no].curX, + swrData[no].curY - swrData[no].lastY, + swrData[no].dydx); + } + + } else { + + if ( swrData[no].curX <= swrData[no].lastX ) { + + line->AddBord(swrData[no].curX, + 0, + swrData[no].lastX, + swrData[no].lastY - swrData[no].curY, + swrData[no].dydx); + + } else if ( swrData[no].curX > swrData[no].lastX ) { + + line->AddBord(swrData[no].lastX, + 0, + swrData[no].curX, + swrData[no].lastY - swrData[no].curY, + -swrData[no].dydx); + } + } +} + +/** + * \param P point index. + * \param numberUp Filled in with the number of edges coming into P from above. + * \param numberDown Filled in with the number of edges coming exiting P to go below. + * \param upEdge One of the numberUp edges, or -1. + * \param downEdge One of the numberDown edges, or -1. + */ + +void Shape::_countUpDown(int P, int *numberUp, int *numberDown, int *upEdge, int *downEdge) const +{ + *numberUp = 0; + *numberDown = 0; + *upEdge = -1; + *downEdge = -1; + + int i = getPoint(P).incidentEdge[FIRST]; + + while ( i >= 0 && i < numberOfEdges() ) { + Shape::dg_arete const &e = getEdge(i); + if ( P == std::max(e.st, e.en) ) { + *upEdge = i; + (*numberUp)++; + } + if ( P == std::min(e.st, e.en) ) { + *downEdge = i; + (*numberDown)++; + } + i = NextAt(P, i); + } + +} + + + +/** + * Version of Shape::_countUpDown optimised for the case when getPoint(P).totalDegree() == 2. + */ + +void Shape::_countUpDownTotalDegree2(int P, + int *numberUp, int *numberDown, int *upEdge, int *downEdge) const +{ + *numberUp = 0; + *numberDown = 0; + *upEdge = -1; + *downEdge = -1; + + for (int j : getPoint(P).incidentEdge) { + Shape::dg_arete const &e = getEdge(j); + if ( P == std::max(e.st, e.en) ) { + *upEdge = j; + (*numberUp)++; + } + if ( P == std::min(e.st, e.en) ) { + *downEdge = j; + (*numberDown)++; + } + } +} + + +void Shape::_updateIntersection(int e, int p) +{ + swrData[e].lastX = swrData[e].curX; + swrData[e].lastY = swrData[e].curY; + swrData[e].curX = getPoint(p).x[0]; + swrData[e].curY = getPoint(p).x[1]; + swrData[e].misc = nullptr; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/livarot/ShapeSweep.cpp b/src/livarot/ShapeSweep.cpp new file mode 100644 index 0000000..c139d80 --- /dev/null +++ b/src/livarot/ShapeSweep.cpp @@ -0,0 +1,3637 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <glib.h> +#include <2geom/affine.h> +#include "Shape.h" +#include "livarot/sweep-event-queue.h" +#include "livarot/sweep-tree-list.h" +#include "livarot/sweep-tree.h" + +//int doDebug=0; + +/* + * El Intersector. + * algorithm: 1) benley ottman to get intersections of all the polygon's edges + * 2) rounding of the points of the polygon, Hooby's algorithm + * 3) DFS with clockwise choice of the edge to compute the windings + * 4) choose edges according to winding numbers and fill rule + * some additional nastyness: step 2 needs a seed winding number for the upper-left point of each + * connex subgraph of the graph. computing these brutally is O(n^3): baaaad. so during the sweeping in 1) + * we keep for each point the edge of the resulting graph (not the original) that lies just on its left; + * when the time comes for the point to get its winding number computed, that edge must have been treated, + * because its upper end lies above the aforementioned point, meaning we know the winding number of the point. + * only, there is a catch: since we're sweeping the polygon, the edge we want to link the point to has not yet been + * added (that would be too easy...). so the points are put on a linked list on the original shape's edge, and the list + * is flushed when the edge is added. + * rounding: to do the rounding, we need to find which edges cross the surrounding of the rounded points (at + * each sweepline position). grunt method tries all combination of "rounded points in the sweepline"x"edges crossing + * the sweepline". That's bad (and that's what polyboolean does, if i am not mistaken). so for each point + * rounded in a given sweepline, keep immediate left and right edges at the time the point is treated. + * when edges/points crossing are searched, walk the edge list (in the sweepline at the end of the batch) starting + * from the rounded points' left and right from that time. may sound strange, but it works because edges that + * end or start in the batch have at least one end in the batch. + * all these are the cause of the numerous linked lists of points and edges maintained in the sweeping + */ + +void +Shape::ResetSweep () +{ + MakePointData (true); + MakeEdgeData (true); + MakeSweepSrcData (true); +} + +void +Shape::CleanupSweep () +{ + MakePointData (false); + MakeEdgeData (false); + MakeSweepSrcData (false); +} + +void +Shape::ForceToPolygon () +{ + type = shape_polygon; +} + +int +Shape::Reoriente (Shape * a) +{ + Reset (0, 0); + if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1) + return 0; + if (directedEulerian(a) == false) + return shape_input_err; + + _pts = a->_pts; + if (numberOfPoints() > maxPt) + { + maxPt = numberOfPoints(); + if (_has_points_data) { + pData.resize(maxPt); + _point_data_initialised = false; + _bbox_up_to_date = false; + } + } + + _aretes = a->_aretes; + if (numberOfEdges() > maxAr) + { + maxAr = numberOfEdges(); + if (_has_edges_data) + eData.resize(maxAr); + if (_has_sweep_src_data) + swsData.resize(maxAr); + if (_has_sweep_dest_data) + swdData.resize(maxAr); + if (_has_raster_data) + swrData.resize(maxAr); + } + + MakePointData (true); + MakeEdgeData (true); + MakeSweepDestData (true); + + initialisePointData(); + + for (int i = 0; i < numberOfPoints(); i++) { + _pts[i].x = pData[i].rx; + _pts[i].oldDegree = getPoint(i).totalDegree(); + } + + for (int i = 0; i < a->numberOfEdges(); i++) + { + eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx; + eData[i].weight = 1; + _aretes[i].dx = eData[i].rdx; + } + + SortPointsRounded (); + + _need_edges_sorting = true; + GetWindings (this, nullptr, bool_op_union, true); + +// Plot(341,56,8,400,400,true,true,false,true); + for (int i = 0; i < numberOfEdges(); i++) + { + swdData[i].leW %= 2; + swdData[i].riW %= 2; + if (swdData[i].leW < 0) + swdData[i].leW = -swdData[i].leW; + if (swdData[i].riW < 0) + swdData[i].riW = -swdData[i].riW; + if (swdData[i].leW > 0 && swdData[i].riW <= 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW <= 0 && swdData[i].riW > 0) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + + MakePointData (false); + MakeEdgeData (false); + MakeSweepDestData (false); + + if (directedEulerian(this) == false) + { +// printf( "pas euclidian2"); + _pts.clear(); + _aretes.clear(); + return shape_euler_err; + } + + type = shape_polygon; + return 0; +} + +int +Shape::ConvertToShape (Shape * a, FillRule directed, bool invert) +{ + // reset any existing stuff in this shape + Reset (0, 0); + + // nothing to do with 0/1 points/edges + if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1) { + return 0; + } + + // if shape is not eulerian, we can't proceedA, why though you might ask? + // I think because if it's not eulerian, winding numbers won't be consistent and thus + // nothing else will work + if ( directed != fill_justDont && directedEulerian(a) == false ) { + g_warning ("Shape error in ConvertToShape: directedEulerian(a) == false\n"); + return shape_input_err; + } + + a->ResetSweep(); + + // allocating the sweepline data structures + if (sTree == nullptr) { + sTree = new SweepTreeList(a->numberOfEdges()); + } + if (sEvts == nullptr) { + sEvts = new SweepEventQueue(a->numberOfEdges()); + } + + // make room for stuff and set flags + MakePointData(true); + MakeEdgeData(true); + MakeSweepSrcData(true); + MakeSweepDestData(true); + MakeBackData(a->_has_back_data); + + // initialize pData and eData arrays, stores rounded points, rounded edge vectors and their lengths + a->initialisePointData(); + a->initialiseEdgeData(); + + // sort points of a, top to bottom so we can sweepline + a->SortPointsRounded(); + + // clear the chgts array + chgts.clear(); + + double lastChange = a->pData[0].rx[1] - 1.0; + int lastChgtPt = 0; + int edgeHead = -1; + Shape *shapeHead = nullptr; + + clearIncidenceData(); + + // index of the current point in shape a + int curAPt = 0; + + // as long as there is a point we haven't seen yet or events that we haven't popped yet + while (curAPt < a->numberOfPoints() || sEvts->size() > 0) { + Geom::Point ptX; + double ptL, ptR; + SweepTree *intersL = nullptr; + SweepTree *intersR = nullptr; + int nPt = -1; + Shape *ptSh = nullptr; + bool isIntersection = false; + + // this block gives us the earliest most point (sweeping top to bottom and left to right) + // whether it comes from the intersection event priority queue sEvts or sweepline list sTree. + // isIntersection tell us if it's coming from an intersection or from the endpoints list of shape a + // ptX contains the actual point itself + // ptSh contains the shape from which the point comes (if it does) (shape a always) + // nPt is the index of the point if it's coming from a shape + + // is there an intersection event to pop? + if (sEvts->peek(intersL, intersR, ptX, ptL, ptR)) + { + // is that intersection event before the current point in shape a? If yes, we pop and process the intersection event otherwise + // we process the point in shape a, whichever comes first (sweeping top to bottom) the one with smaller y or smaller x (if same y) + if (a->pData[curAPt].pending > 0 + || (a->pData[curAPt].rx[1] > ptX[1] + || (a->pData[curAPt].rx[1] == ptX[1] + && a->pData[curAPt].rx[0] > ptX[0]))) + { + // if yes, let's process the intersection point + /* FIXME: could just be pop? */ + sEvts->extract(intersL, intersR, ptX, ptL, ptR); + isIntersection = true; + } + else // otherwise, we process the current point in shape a + { + nPt = curAPt++; // nPt stores the index of this point in shape a that we are going to process + ptSh = a; + ptX = ptSh->pData[nPt].rx; // get the rounded version of the current point in ptX + isIntersection = false; // not an intersection + } + } + else + { + nPt = curAPt++; + ptSh = a; + ptX = ptSh->pData[nPt].rx; + isIntersection = false; + } + + if (isIntersection == false) // if this is not intersection event and the point has total degree of 0, we have nothing to do + { + if (ptSh->getPoint(nPt).dI == 0 && ptSh->getPoint(nPt).dO == 0) + continue; + } + + // wherever the point comes from, we add the rounded version to "this" shape's list of points + Geom::Point rPtX; + rPtX[0]= Round (ptX[0]); + rPtX[1]= Round (ptX[1]); + int lastPointNo = AddPoint (rPtX); // lastPointNo is the index of this last rounded point we added + pData[lastPointNo].rx = rPtX; + + // this whole block deals with the reconstruction procedure only do this if the y level + // changed, we don't need to do this as long as the y level has not changed, note that + // sweepline in this algorithm moves top to bottom but also left to right when there are + // multiple points at same y better to think of it as the sweepline being slightly slanted such + // that it'll hit the left points earlier than the right ones In fact, it's better to visualize + // as if we are iterating through set of points top to bottom and left ot right instead of a + // sweepline. + if (rPtX[1] > lastChange) + { + // the important thing this function does is that it sorts points and merges any duplicate points + // so edges with different points (which were at the same coordinates) would be forced to point + // to the same exact point. (useful for cases when multiple edges intersect at the same point) + // Sort all points before lastPointNo + int lastI = AssemblePoints (lastChgtPt, lastPointNo); // lastI is one more than the index of the last point that + // was added by AssemblePoints after sorting and merging + + // after AssemblePoints is done sorting, the newInd variable holds the new index of that point + + // update the leftRnd and rightRnd indexes to newInd while traversing the linked list of + // edges and shapes. leftRnd and rightRnd are the left most and right most points of an edge + // in the resultant polygon that intersect the sweepline. In all non-horizontal edges, both + // would be identical, only when the edge is horizontal, the two will be different since the + // sweepline will intersect it at multiple endpoints. + // To define it more strictly, you can think of leftRnd and rightRnd as being the left most + // and right most point in the final resulted shape ("this" shape) at the y level lastChange. + Shape *curSh = shapeHead; + int curBo = edgeHead; + while (curSh) + { + curSh->swsData[curBo].leftRnd = + pData[curSh->swsData[curBo].leftRnd].newInd; + curSh->swsData[curBo].rightRnd = + pData[curSh->swsData[curBo].rightRnd].newInd; + + Shape *neSh = curSh->swsData[curBo].nextSh; + curBo = curSh->swsData[curBo].nextBo; + curSh = neSh; + } + + for (auto & chgt : chgts) + { + chgt.ptNo = pData[chgt.ptNo].newInd; // this updates the ptNo index to the new index, so identical points really merge + if (chgt.type == 0) + { + if (chgt.src->getEdge(chgt.bord).st < + chgt.src->getEdge(chgt.bord).en) + { + chgt.src->swsData[chgt.bord].stPt = // <-- No idea where stPt and enPt are really used + chgt.ptNo; + } + else + { + chgt.src->swsData[chgt.bord].enPt = + chgt.ptNo; + } + } + else if (chgt.type == 1) + { + if (chgt.src->getEdge(chgt.bord).st > + chgt.src->getEdge(chgt.bord).en) + { + chgt.src->swsData[chgt.bord].stPt = + chgt.ptNo; + } + else + { + chgt.src->swsData[chgt.bord].enPt = + chgt.ptNo; + } + } + } + + // finds if points at the y level "lastChange" lie on top of the edge and if yes, modifies + // leftRnd and rightRnd of those edges accordingly + CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead); + + // reconstruct the edges + CheckEdges (lastI, lastChgtPt, a, nullptr, bool_op_union); + + // for each one of the points in the range lastChgtPt..(lastI-1) and if there are already + // points associated to the edge pData[i].askForWindingB, push the current one (i) in the linked + // list + for (int i = lastChgtPt; i < lastI; i++) { + if (pData[i].askForWindingS) { + Shape *windS = pData[i].askForWindingS; + int windB = pData[i].askForWindingB; + pData[i].nextLinkedPoint = windS->swsData[windB].firstLinkedPoint; + windS->swsData[windB].firstLinkedPoint = i; + } + } + + // note that AssemblePoints doesn't even touch lastPointNo, it iterates on all the points before + // lastPointNo, so while that range might have shrinked because of duplicate points, lastPointNo + // remains where it was, so we check if some points got merged, if yes, lastPointNo is kinda far away + // from those points (there are garbage points in between due to merging), so we drag it back + if (lastI < lastPointNo) { + _pts[lastI] = getPoint(lastPointNo); + pData[lastI] = pData[lastPointNo]; + } + // set lastPointNo to lastI (the new index for lastPointNo) + lastPointNo = lastI; + _pts.resize(lastI + 1); // resize and delete everything beyond lastPointNo, we add 1 cuz lastI is an index so u + // add one to convert it to size + + lastChgtPt = lastPointNo; // set lastChgtPt + lastChange = rPtX[1]; // set lastChange + chgts.clear(); // this chgts array gets cleared up whenever the y of the sweepline changes + edgeHead = -1; + shapeHead = nullptr; + } + + + // are we processing an intersection point? + if (isIntersection) + { + // printf("(%i %i [%i %i]) ",intersL->bord,intersR->bord,intersL->startPoint,intersR->startPoint); + // remove any intersections that have interesL as a RIGHT edge + intersL->RemoveEvent (*sEvts, LEFT); + // remove any intersections that have intersR as a LEFT edge + intersR->RemoveEvent (*sEvts, RIGHT); + + // add the intersection point to the chgts array + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, INTERSECTION, + intersL->src, intersL->bord, intersR->src, intersR->bord); + + // swap the edges intersL and intersR with each other (because they swap their intersection point with the sweepline as + // you pass the intersection point) + intersL->SwapWithRight (*sTree, *sEvts); + + // test intersection of the now left edge with the one on its left + TesteIntersection (intersL, LEFT, false); + // test intersection of the now right edge with the one on its right + TesteIntersection (intersR, RIGHT, false); + } + else + { // we are doing with a point in shape a (not an intersection point) + int cb; + + // this whole block: + // - Counts how many edges start at the current point (nbDn) + // - Counts how many edges end at the current point (nbUp) + // - Notes the last edge that starts here (dnNo) + // - Notes the last edge that ends here (upNo) + int nbUp = 0, nbDn = 0; + int upNo = -1, dnNo = -1; + cb = ptSh->getPoint(nPt).incidentEdge[FIRST]; + while (cb >= 0 && cb < ptSh->numberOfEdges()) + { + if ((ptSh->getEdge(cb).st < ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).en) + || (ptSh->getEdge(cb).st > ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).st)) + { + upNo = cb; + nbUp++; + } + if ((ptSh->getEdge(cb).st > ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).en) + || (ptSh->getEdge(cb).st < ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).st)) + { + dnNo = cb; + nbDn++; + } + cb = ptSh->NextAt (nPt, cb); + } + + if (nbDn <= 0) + { + upNo = -1; + } + if (upNo >= 0 && (SweepTree *) ptSh->swsData[upNo].misc == nullptr) + { + upNo = -1; + } + + bool doWinding = true; + + // these blocks of code do the job of adding/removing edges + // however, there are some optimizations which leads to their weird if blocks + + // Is there any edge that ends here? + if (nbUp > 0) + { + cb = ptSh->getPoint(nPt).incidentEdge[FIRST]; + // for all edges that connect to this point + while (cb >= 0 && cb < ptSh->numberOfEdges()) + { // if the edge ends here + if ((ptSh->getEdge(cb).st < ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).en) + || (ptSh->getEdge(cb).st > ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).st)) + { + if (cb != upNo) // if this is not the last edge that ended at this point + { + SweepTree *node = + (SweepTree *) ptSh->swsData[cb].misc; // get the sweepline node for this edge + if (node == nullptr) + { + } + else + { + AddChgt (lastPointNo, lastChgtPt, shapeHead, // add the corresponding remove event in chgts + edgeHead, EDGE_REMOVED, node->src, node->bord, + nullptr, -1); + ptSh->swsData[cb].misc = nullptr; + + int onLeftB = -1, onRightB = -1; + Shape *onLeftS = nullptr; + Shape *onRightS = nullptr; + if (node->elem[LEFT]) + { + onLeftB = + (static_cast < + SweepTree * >(node->elem[LEFT]))->bord; + onLeftS = + (static_cast < + SweepTree * >(node->elem[LEFT]))->src; + } + if (node->elem[RIGHT]) + { + onRightB = + (static_cast < + SweepTree * >(node->elem[RIGHT]))->bord; + onRightS = + (static_cast < + SweepTree * >(node->elem[RIGHT]))->src; + } + + node->Remove (*sTree, *sEvts, true); // remove this edge + // if there are edges on the left and right and they do not start or end at the current point + // test if they interesect with each other (since now this edge just go removed and they are side to side) + if (onLeftS && onRightS) + { + SweepTree *onLeft = + (SweepTree *) onLeftS->swsData[onLeftB]. + misc; + if (onLeftS == ptSh + && (onLeftS->getEdge(onLeftB).en == nPt + || onLeftS->getEdge(onLeftB).st == + nPt)) + { + } + else + { + if (onRightS == ptSh + && (onRightS->getEdge(onRightB).en == + nPt + || onRightS->getEdge(onRightB). + st == nPt)) + { + } + else + { + TesteIntersection (onLeft, RIGHT, false); + } + } + } + } + } + } + cb = ptSh->NextAt (nPt, cb); + } + } + + // traitement du "upNo devient dnNo" + SweepTree *insertionNode = nullptr; + if (dnNo >= 0) // if there is a last edge that started here + { + if (upNo >= 0) // and there is a last edge that ended here + { + SweepTree *node = (SweepTree *) ptSh->swsData[upNo].misc; + + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_REMOVED, // add edge removal event to the list + node->src, node->bord, nullptr, -1); + + ptSh->swsData[upNo].misc = nullptr; + + node->RemoveEvents (*sEvts); // remove any events associated with this node + node->ConvertTo (ptSh, dnNo, 1, lastPointNo); // convert this node the last edge that got added at this point + ptSh->swsData[dnNo].misc = node; // store the sweepline edge tree node at misc for later use + TesteIntersection (node, RIGHT, false); // test the interesction of this node with the one on its right + TesteIntersection (node, LEFT, false); // test the interesection of this node with the one on its left + insertionNode = node; // a variable to keep the pointer to this node for later use + + ptSh->swsData[dnNo].curPoint = lastPointNo; // mark the curPoint in swsData for later use in reconstruction + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED, + node->src, node->bord, nullptr, -1); // add the edge insertion event to chgts + } + else // if there is a last edge that started at this point but not any that ended + { + SweepTree *node = sTree->add(ptSh, dnNo, 1, lastPointNo, this); // add this edge + ptSh->swsData[dnNo].misc = node; // store in misc too + node->Insert (*sTree, *sEvts, this, lastPointNo, true); // insert the node at its right location in the sweepline tree + if (doWinding) + { + SweepTree *myLeft = + static_cast < SweepTree * >(node->elem[LEFT]); + if (myLeft) + { + pData[lastPointNo].askForWindingS = myLeft->src; + pData[lastPointNo].askForWindingB = myLeft->bord; + } + else + { + pData[lastPointNo].askForWindingB = -1; + } + doWinding = false; + } + TesteIntersection (node, RIGHT, false); // test intersection of this newly inserted node with the one on its right + TesteIntersection (node, LEFT, false); // test intersection of this newly inserted node with the one on its left + insertionNode = node; // store insertionNode for later use + + ptSh->swsData[dnNo].curPoint = lastPointNo; // mark the curPoint appropriately + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED, // add the edge inserted event + node->src, node->bord, nullptr, -1); + } + } + + if (nbDn > 1) // if there are more than 1 edges that start at this point + { // si nbDn == 1 , alors dnNo a deja ete traite + cb = ptSh->getPoint(nPt).incidentEdge[FIRST]; + while (cb >= 0 && cb < ptSh->numberOfEdges()) // for all edges that connect to this point + { + if ((ptSh->getEdge(cb).st > ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).en) + || (ptSh->getEdge(cb).st < ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).st)) + { // if the edge starts here + if (cb != dnNo) + { + SweepTree *node = sTree->add(ptSh, cb, 1, lastPointNo, this); // add the node to the tree + ptSh->swsData[cb].misc = node; + node->InsertAt (*sTree, *sEvts, this, insertionNode, // insert it at appropriate position + nPt, true); + if (doWinding) + { + SweepTree *myLeft = + static_cast < SweepTree * >(node->elem[LEFT]); + if (myLeft) + { + pData[lastPointNo].askForWindingS = + myLeft->src; + pData[lastPointNo].askForWindingB = + myLeft->bord; + } + else + { + pData[lastPointNo].askForWindingB = -1; + } + doWinding = false; + } + TesteIntersection (node, RIGHT, false); // test intersection of this edge with one on its left + TesteIntersection (node, LEFT, false); // test intersection of this edge with one on its right + + ptSh->swsData[cb].curPoint = lastPointNo; // store curPoint in sws + AddChgt (lastPointNo, lastChgtPt, shapeHead, // add appropriate edge insertion event in chgts + edgeHead, EDGE_INSERTED, node->src, node->bord, nullptr, + -1); + } + } + cb = ptSh->NextAt (nPt, cb); + } + } + } + } + // a code block totally identical to the one found above in loop. Has to be run one last time + // so written outside.. + { + int lastI = AssemblePoints (lastChgtPt, numberOfPoints()); + + + Shape *curSh = shapeHead; + int curBo = edgeHead; + while (curSh) + { + curSh->swsData[curBo].leftRnd = + pData[curSh->swsData[curBo].leftRnd].newInd; + curSh->swsData[curBo].rightRnd = + pData[curSh->swsData[curBo].rightRnd].newInd; + + Shape *neSh = curSh->swsData[curBo].nextSh; + curBo = curSh->swsData[curBo].nextBo; + curSh = neSh; + } + + for (auto & chgt : chgts) + { + chgt.ptNo = pData[chgt.ptNo].newInd; + if (chgt.type == 0) + { + if (chgt.src->getEdge(chgt.bord).st < + chgt.src->getEdge(chgt.bord).en) + { + chgt.src->swsData[chgt.bord].stPt = chgt.ptNo; + } + else + { + chgt.src->swsData[chgt.bord].enPt = chgt.ptNo; + } + } + else if (chgt.type == 1) + { + if (chgt.src->getEdge(chgt.bord).st > + chgt.src->getEdge(chgt.bord).en) + { + chgt.src->swsData[chgt.bord].stPt = chgt.ptNo; + } + else + { + chgt.src->swsData[chgt.bord].enPt = chgt.ptNo; + } + } + } + + CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead); + + CheckEdges (lastI, lastChgtPt, a, nullptr, bool_op_union); + + for (int i = lastChgtPt; i < lastI; i++) + { + if (pData[i].askForWindingS) + { + Shape *windS = pData[i].askForWindingS; + int windB = pData[i].askForWindingB; + pData[i].nextLinkedPoint = windS->swsData[windB].firstLinkedPoint; + windS->swsData[windB].firstLinkedPoint = i; + } + } + + _pts.resize(lastI); + + edgeHead = -1; + shapeHead = nullptr; + } + + chgts.clear(); + + // Plot (98.0, 112.0, 8.0, 400.0, 400.0, true, true, true, true); + // Plot(200.0,200.0,2.0,400.0,400.0,true,true,true,true); + + // AssemblePoints(a); + + // GetAdjacencies(a); + + // MakeAretes(a); + clearIncidenceData(); + + // deal with doublon edges (edges on top of each other) + // we only keep one edge and set its weight equivalent to the net difference of the edges. + // If two edges are exactly identical and in same direction their weights add up, if they + // are in the opposite direction, weights are subtracted. The other edges are removed. + AssembleAretes (directed); + + // Plot (98.0, 112.0, 8.0, 400.0, 400.0, true, true, true, true); + + // store the degrees at this point in time + for (int i = 0; i < numberOfPoints(); i++) + { + _pts[i].oldDegree = getPoint(i).totalDegree(); + } + // Validate(); + + _need_edges_sorting = true; + if ( directed == fill_justDont ) { + SortEdges(); // sorting edges + } else { + GetWindings (a); // getting winding numbers of the edges + } + // Plot (98.0, 112.0, 8.0, 400.0, 400.0, true, true, true, true); + // if ( doDebug ) { + // a->CalcBBox(); + // a->Plot(a->leftX,a->topY,32.0,0.0,0.0,true,true,true,true,"orig.svg"); + // Plot(a->leftX,a->topY,32.0,0.0,0.0,true,true,true,true,"winded.svg"); + // } + + // change edges depending on the fill rule. Decide whether to keep it, invert it or get rid of it + if (directed == fill_positive) + { + if (invert) + { + for (int i = 0; i < numberOfEdges(); i++) + { + if (swdData[i].leW < 0 && swdData[i].riW >= 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW >= 0 && swdData[i].riW < 0) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + } + else + { + for (int i = 0; i < numberOfEdges(); i++) + { + if (swdData[i].leW > 0 && swdData[i].riW <= 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW <= 0 && swdData[i].riW > 0) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + } + } + else if (directed == fill_nonZero) + { + if (invert) + { + for (int i = 0; i < numberOfEdges(); i++) + { + if (swdData[i].leW < 0 && swdData[i].riW == 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW > 0 && swdData[i].riW == 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW == 0 && swdData[i].riW < 0) + { + Inverse (i); + eData[i].weight = 1; + } + else if (swdData[i].leW == 0 && swdData[i].riW > 0) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + } + else + { + for (int i = 0; i < numberOfEdges(); i++) + { + if (swdData[i].leW > 0 && swdData[i].riW == 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW < 0 && swdData[i].riW == 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW == 0 && swdData[i].riW > 0) + { + Inverse (i); + eData[i].weight = 1; + } + else if (swdData[i].leW == 0 && swdData[i].riW < 0) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + } + } + else if (directed == fill_oddEven) + { + for (int i = 0; i < numberOfEdges(); i++) + { + swdData[i].leW %= 2; + swdData[i].riW %= 2; + if (swdData[i].leW < 0) + swdData[i].leW = -swdData[i].leW; + if (swdData[i].riW < 0) + swdData[i].riW = -swdData[i].riW; + if (swdData[i].leW > 0 && swdData[i].riW <= 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW <= 0 && swdData[i].riW > 0) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + } else if ( directed == fill_justDont ) { + for (int i=0;i<numberOfEdges();i++) { + if ( getEdge(i).st < 0 || getEdge(i).en < 0 ) { + SubEdge(i); + i--; + } else { + eData[i].weight = 0; + } + } + } + + // Plot(200.0,200.0,2.0,400.0,400.0,true,true,true,true); + + delete sTree; + sTree = nullptr; + delete sEvts; + sEvts = nullptr; + + MakePointData (false); + MakeEdgeData (false); + MakeSweepSrcData (false); + MakeSweepDestData (false); + a->CleanupSweep (); + type = shape_polygon; + return 0; +} + +// technically it's just a ConvertToShape() on 2 polygons at the same time, and different rules +// for choosing the edges according to their winding numbers. +// probably one of the biggest function i ever wrote. +int +Shape::Booleen (Shape * a, Shape * b, BooleanOp mod,int cutPathID) +{ + if (a == b || a == nullptr || b == nullptr) + return shape_input_err; + Reset (0, 0); + if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1) + return 0; + if (b->numberOfPoints() <= 1 || b->numberOfEdges() <= 1) + return 0; + if ( mod == bool_op_cut ) { + } else if ( mod == bool_op_slice ) { + } else { + if (a->type != shape_polygon) + return shape_input_err; + if (b->type != shape_polygon) + return shape_input_err; + } + + a->ResetSweep (); + b->ResetSweep (); + + if (sTree == nullptr) { + sTree = new SweepTreeList(a->numberOfEdges() + b->numberOfEdges()); + } + if (sEvts == nullptr) { + sEvts = new SweepEventQueue(a->numberOfEdges() + b->numberOfEdges()); + } + + MakePointData (true); + MakeEdgeData (true); + MakeSweepSrcData (true); + MakeSweepDestData (true); + if (a->hasBackData () && b->hasBackData ()) + { + MakeBackData (true); + } + else + { + MakeBackData (false); + } + + a->initialisePointData(); + b->initialisePointData(); + + a->initialiseEdgeData(); + b->initialiseEdgeData(); + + a->SortPointsRounded (); + b->SortPointsRounded (); + + chgts.clear(); + + double lastChange = + (a->pData[0].rx[1] < + b->pData[0].rx[1]) ? a->pData[0].rx[1] - 1.0 : b->pData[0].rx[1] - 1.0; + int lastChgtPt = 0; + int edgeHead = -1; + Shape *shapeHead = nullptr; + + clearIncidenceData(); + + int curAPt = 0; + int curBPt = 0; + + while (curAPt < a->numberOfPoints() || curBPt < b->numberOfPoints() || sEvts->size() > 0) + { +/* for (int i=0;i<sEvts.nbEvt;i++) { + printf("%f %f %i %i\n",sEvts.events[i].posx,sEvts.events[i].posy,sEvts.events[i].leftSweep->bord,sEvts.events[i].rightSweep->bord); // localizing ok + } + // cout << endl; + if ( sTree.racine ) { + SweepTree* ct=static_cast <SweepTree*> (sTree.racine->Leftmost()); + while ( ct ) { + printf("%i %i [%i\n",ct->bord,ct->startPoint,(ct->src==a)?1:0); + ct=static_cast <SweepTree*> (ct->elem[RIGHT]); + } + } + printf("\n");*/ + + Geom::Point ptX; + double ptL, ptR; + SweepTree *intersL = nullptr; + SweepTree *intersR = nullptr; + int nPt = -1; + Shape *ptSh = nullptr; + bool isIntersection = false; + + if (sEvts->peek(intersL, intersR, ptX, ptL, ptR)) + { + if (curAPt < a->numberOfPoints()) + { + if (curBPt < b->numberOfPoints()) + { + if (a->pData[curAPt].rx[1] < b->pData[curBPt].rx[1] + || (a->pData[curAPt].rx[1] == b->pData[curBPt].rx[1] + && a->pData[curAPt].rx[0] < b->pData[curBPt].rx[0])) + { + if (a->pData[curAPt].pending > 0 + || (a->pData[curAPt].rx[1] > ptX[1] + || (a->pData[curAPt].rx[1] == ptX[1] + && a->pData[curAPt].rx[0] > ptX[0]))) + { + /* FIXME: could be pop? */ + sEvts->extract(intersL, intersR, ptX, ptL, ptR); + isIntersection = true; + } + else + { + nPt = curAPt++; + ptSh = a; + ptX = ptSh->pData[nPt].rx; + isIntersection = false; + } + } + else + { + if (b->pData[curBPt].pending > 0 + || (b->pData[curBPt].rx[1] > ptX[1] + || (b->pData[curBPt].rx[1] == ptX[1] + && b->pData[curBPt].rx[0] > ptX[0]))) + { + /* FIXME: could be pop? */ + sEvts->extract(intersL, intersR, ptX, ptL, ptR); + isIntersection = true; + } + else + { + nPt = curBPt++; + ptSh = b; + ptX = ptSh->pData[nPt].rx; + isIntersection = false; + } + } + } + else + { + if (a->pData[curAPt].pending > 0 + || (a->pData[curAPt].rx[1] > ptX[1] + || (a->pData[curAPt].rx[1] == ptX[1] + && a->pData[curAPt].rx[0] > ptX[0]))) + { + /* FIXME: could be pop? */ + sEvts->extract(intersL, intersR, ptX, ptL, ptR); + isIntersection = true; + } + else + { + nPt = curAPt++; + ptSh = a; + ptX = ptSh->pData[nPt].rx; + isIntersection = false; + } + } + } + else + { + if (b->pData[curBPt].pending > 0 + || (b->pData[curBPt].rx[1] > ptX[1] + || (b->pData[curBPt].rx[1] == ptX[1] + && b->pData[curBPt].rx[0] > ptX[0]))) + { + /* FIXME: could be pop? */ + sEvts->extract(intersL, intersR, ptX, ptL, ptR); + isIntersection = true; + } + else + { + nPt = curBPt++; + ptSh = b; + ptX = ptSh->pData[nPt].rx; + isIntersection = false; + } + } + } + else + { + if (curAPt < a->numberOfPoints()) + { + if (curBPt < b->numberOfPoints()) + { + if (a->pData[curAPt].rx[1] < b->pData[curBPt].rx[1] + || (a->pData[curAPt].rx[1] == b->pData[curBPt].rx[1] + && a->pData[curAPt].rx[0] < b->pData[curBPt].rx[0])) + { + nPt = curAPt++; + ptSh = a; + } + else + { + nPt = curBPt++; + ptSh = b; + } + } + else + { + nPt = curAPt++; + ptSh = a; + } + } + else + { + nPt = curBPt++; + ptSh = b; + } + ptX = ptSh->pData[nPt].rx; + isIntersection = false; + } + + if (isIntersection == false) + { + if (ptSh->getPoint(nPt).dI == 0 && ptSh->getPoint(nPt).dO == 0) + continue; + } + + Geom::Point rPtX; + rPtX[0]= Round (ptX[0]); + rPtX[1]= Round (ptX[1]); + int lastPointNo = AddPoint (rPtX); + pData[lastPointNo].rx = rPtX; + + if (rPtX[1] > lastChange) + { + int lastI = AssemblePoints (lastChgtPt, lastPointNo); + + + Shape *curSh = shapeHead; + int curBo = edgeHead; + while (curSh) + { + curSh->swsData[curBo].leftRnd = + pData[curSh->swsData[curBo].leftRnd].newInd; + curSh->swsData[curBo].rightRnd = + pData[curSh->swsData[curBo].rightRnd].newInd; + + Shape *neSh = curSh->swsData[curBo].nextSh; + curBo = curSh->swsData[curBo].nextBo; + curSh = neSh; + } + + for (auto & chgt : chgts) + { + chgt.ptNo = pData[chgt.ptNo].newInd; + if (chgt.type == 0) + { + if (chgt.src->getEdge(chgt.bord).st < + chgt.src->getEdge(chgt.bord).en) + { + chgt.src->swsData[chgt.bord].stPt = + chgt.ptNo; + } + else + { + chgt.src->swsData[chgt.bord].enPt = + chgt.ptNo; + } + } + else if (chgt.type == 1) + { + if (chgt.src->getEdge(chgt.bord).st > + chgt.src->getEdge(chgt.bord).en) + { + chgt.src->swsData[chgt.bord].stPt = + chgt.ptNo; + } + else + { + chgt.src->swsData[chgt.bord].enPt = + chgt.ptNo; + } + } + } + + CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead); + + CheckEdges (lastI, lastChgtPt, a, b, mod); + + for (int i = lastChgtPt; i < lastI; i++) + { + if (pData[i].askForWindingS) + { + Shape *windS = pData[i].askForWindingS; + int windB = pData[i].askForWindingB; + pData[i].nextLinkedPoint = + windS->swsData[windB].firstLinkedPoint; + windS->swsData[windB].firstLinkedPoint = i; + } + } + + if (lastI < lastPointNo) + { + _pts[lastI] = getPoint(lastPointNo); + pData[lastI] = pData[lastPointNo]; + } + lastPointNo = lastI; + _pts.resize(lastI + 1); + + lastChgtPt = lastPointNo; + lastChange = rPtX[1]; + chgts.clear(); + edgeHead = -1; + shapeHead = nullptr; + } + + + if (isIntersection) + { + // les 2 events de part et d'autre de l'intersection + // (celui de l'intersection a deja ete depile) + intersL->RemoveEvent (*sEvts, LEFT); + intersR->RemoveEvent (*sEvts, RIGHT); + + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, INTERSECTION, + intersL->src, intersL->bord, intersR->src, intersR->bord); + + intersL->SwapWithRight (*sTree, *sEvts); + + TesteIntersection (intersL, LEFT, true); + TesteIntersection (intersR, RIGHT, true); + } + else + { + int cb; + + int nbUp = 0, nbDn = 0; + int upNo = -1, dnNo = -1; + cb = ptSh->getPoint(nPt).incidentEdge[FIRST]; + while (cb >= 0 && cb < ptSh->numberOfEdges()) + { + if ((ptSh->getEdge(cb).st < ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).en) + || (ptSh->getEdge(cb).st > ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).st)) + { + upNo = cb; + nbUp++; + } + if ((ptSh->getEdge(cb).st > ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).en) + || (ptSh->getEdge(cb).st < ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).st)) + { + dnNo = cb; + nbDn++; + } + cb = ptSh->NextAt (nPt, cb); + } + + if (nbDn <= 0) + { + upNo = -1; + } + if (upNo >= 0 && (SweepTree *) ptSh->swsData[upNo].misc == nullptr) + { + upNo = -1; + } + +// upNo=-1; + + bool doWinding = true; + + if (nbUp > 0) + { + cb = ptSh->getPoint(nPt).incidentEdge[FIRST]; + while (cb >= 0 && cb < ptSh->numberOfEdges()) + { + if ((ptSh->getEdge(cb).st < ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).en) + || (ptSh->getEdge(cb).st > ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).st)) + { + if (cb != upNo) + { + SweepTree *node = + (SweepTree *) ptSh->swsData[cb].misc; + if (node == nullptr) + { + } + else + { + AddChgt (lastPointNo, lastChgtPt, shapeHead, + edgeHead, EDGE_REMOVED, node->src, node->bord, + nullptr, -1); + ptSh->swsData[cb].misc = nullptr; + + int onLeftB = -1, onRightB = -1; + Shape *onLeftS = nullptr; + Shape *onRightS = nullptr; + if (node->elem[LEFT]) + { + onLeftB = + (static_cast < + SweepTree * >(node->elem[LEFT]))->bord; + onLeftS = + (static_cast < + SweepTree * >(node->elem[LEFT]))->src; + } + if (node->elem[RIGHT]) + { + onRightB = + (static_cast < + SweepTree * >(node->elem[RIGHT]))->bord; + onRightS = + (static_cast < + SweepTree * >(node->elem[RIGHT]))->src; + } + + node->Remove (*sTree, *sEvts, true); + if (onLeftS && onRightS) + { + SweepTree *onLeft = + (SweepTree *) onLeftS->swsData[onLeftB]. + misc; +// SweepTree* onRight=(SweepTree*)onRightS->swsData[onRightB].misc; + if (onLeftS == ptSh + && (onLeftS->getEdge(onLeftB).en == nPt + || onLeftS->getEdge(onLeftB).st == + nPt)) + { + } + else + { + if (onRightS == ptSh + && (onRightS->getEdge(onRightB).en == + nPt + || onRightS->getEdge(onRightB). + st == nPt)) + { + } + else + { + TesteIntersection (onLeft, RIGHT, true); + } + } + } + } + } + } + cb = ptSh->NextAt (nPt, cb); + } + } + + // traitement du "upNo devient dnNo" + SweepTree *insertionNode = nullptr; + if (dnNo >= 0) + { + if (upNo >= 0) + { + SweepTree *node = (SweepTree *) ptSh->swsData[upNo].misc; + + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_REMOVED, + node->src, node->bord, nullptr, -1); + + ptSh->swsData[upNo].misc = nullptr; + + node->RemoveEvents (*sEvts); + node->ConvertTo (ptSh, dnNo, 1, lastPointNo); + ptSh->swsData[dnNo].misc = node; + TesteIntersection (node, RIGHT, true); + TesteIntersection (node, LEFT, true); + insertionNode = node; + + ptSh->swsData[dnNo].curPoint = lastPointNo; + + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED, + node->src, node->bord, nullptr, -1); + } + else + { + SweepTree *node = sTree->add(ptSh, dnNo, 1, lastPointNo, this); + ptSh->swsData[dnNo].misc = node; + node->Insert (*sTree, *sEvts, this, lastPointNo, true); + + if (doWinding) + { + SweepTree *myLeft = + static_cast < SweepTree * >(node->elem[LEFT]); + if (myLeft) + { + pData[lastPointNo].askForWindingS = myLeft->src; + pData[lastPointNo].askForWindingB = myLeft->bord; + } + else + { + pData[lastPointNo].askForWindingB = -1; + } + doWinding = false; + } + + TesteIntersection (node, RIGHT, true); + TesteIntersection (node, LEFT, true); + insertionNode = node; + + ptSh->swsData[dnNo].curPoint = lastPointNo; + + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED, + node->src, node->bord, nullptr, -1); + } + } + + if (nbDn > 1) + { // si nbDn == 1 , alors dnNo a deja ete traite + cb = ptSh->getPoint(nPt).incidentEdge[FIRST]; + while (cb >= 0 && cb < ptSh->numberOfEdges()) + { + if ((ptSh->getEdge(cb).st > ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).en) + || (ptSh->getEdge(cb).st < ptSh->getEdge(cb).en + && nPt == ptSh->getEdge(cb).st)) + { + if (cb != dnNo) + { + SweepTree *node = sTree->add(ptSh, cb, 1, lastPointNo, this); + ptSh->swsData[cb].misc = node; +// node->Insert(sTree,*sEvts,this,lastPointNo,true); + node->InsertAt (*sTree, *sEvts, this, insertionNode, + nPt, true); + + if (doWinding) + { + SweepTree *myLeft = + static_cast < SweepTree * >(node->elem[LEFT]); + if (myLeft) + { + pData[lastPointNo].askForWindingS = + myLeft->src; + pData[lastPointNo].askForWindingB = + myLeft->bord; + } + else + { + pData[lastPointNo].askForWindingB = -1; + } + doWinding = false; + } + + TesteIntersection (node, RIGHT, true); + TesteIntersection (node, LEFT, true); + + ptSh->swsData[cb].curPoint = lastPointNo; + + AddChgt (lastPointNo, lastChgtPt, shapeHead, + edgeHead, EDGE_INSERTED, node->src, node->bord, nullptr, + -1); + } + } + cb = ptSh->NextAt (nPt, cb); + } + } + } + } + { + int lastI = AssemblePoints (lastChgtPt, numberOfPoints()); + + + Shape *curSh = shapeHead; + int curBo = edgeHead; + while (curSh) + { + curSh->swsData[curBo].leftRnd = + pData[curSh->swsData[curBo].leftRnd].newInd; + curSh->swsData[curBo].rightRnd = + pData[curSh->swsData[curBo].rightRnd].newInd; + + Shape *neSh = curSh->swsData[curBo].nextSh; + curBo = curSh->swsData[curBo].nextBo; + curSh = neSh; + } + + /* FIXME: this kind of code seems to appear frequently */ + for (auto & chgt : chgts) + { + chgt.ptNo = pData[chgt.ptNo].newInd; + if (chgt.type == 0) + { + if (chgt.src->getEdge(chgt.bord).st < + chgt.src->getEdge(chgt.bord).en) + { + chgt.src->swsData[chgt.bord].stPt = chgt.ptNo; + } + else + { + chgt.src->swsData[chgt.bord].enPt = chgt.ptNo; + } + } + else if (chgt.type == 1) + { + if (chgt.src->getEdge(chgt.bord).st > + chgt.src->getEdge(chgt.bord).en) + { + chgt.src->swsData[chgt.bord].stPt = chgt.ptNo; + } + else + { + chgt.src->swsData[chgt.bord].enPt = chgt.ptNo; + } + } + } + + CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead); + + CheckEdges (lastI, lastChgtPt, a, b, mod); + + for (int i = lastChgtPt; i < lastI; i++) + { + if (pData[i].askForWindingS) + { + Shape *windS = pData[i].askForWindingS; + int windB = pData[i].askForWindingB; + pData[i].nextLinkedPoint = windS->swsData[windB].firstLinkedPoint; + windS->swsData[windB].firstLinkedPoint = i; + } + } + + _pts.resize(lastI); + + edgeHead = -1; + shapeHead = nullptr; + } + + chgts.clear(); + clearIncidenceData(); + +// Plot(190,70,6,400,400,true,false,true,true); + + if ( mod == bool_op_cut ) { + AssembleAretes (fill_justDont); + // dupliquer les aretes de la coupure + int i=numberOfEdges()-1; + for (;i>=0;i--) { + if ( ebData[i].pathID == cutPathID ) { + // on duplique + int nEd=AddEdge(getEdge(i).en,getEdge(i).st); + ebData[nEd].pathID=cutPathID; + ebData[nEd].pieceID=ebData[i].pieceID; + ebData[nEd].tSt=ebData[i].tEn; + ebData[nEd].tEn=ebData[i].tSt; + eData[nEd].weight=eData[i].weight; + // lui donner les firstlinkedpoitn si besoin + if ( getEdge(i).en >= getEdge(i).st ) { + int cp = swsData[i].firstLinkedPoint; + while (cp >= 0) { + pData[cp].askForWindingB = nEd; + cp = pData[cp].nextLinkedPoint; + } + swsData[nEd].firstLinkedPoint = swsData[i].firstLinkedPoint; + swsData[i].firstLinkedPoint=-1; + } + } + } + } else if ( mod == bool_op_slice ) { + } else { + AssembleAretes (); + } + + for (int i = 0; i < numberOfPoints(); i++) + { + _pts[i].oldDegree = getPoint(i).totalDegree(); + } + + _need_edges_sorting = true; + if ( mod == bool_op_slice ) { + } else { + GetWindings (a, b, mod, false); + } +// Plot(190,70,6,400,400,true,true,true,true); + + if (mod == bool_op_symdiff) + { + for (int i = 0; i < numberOfEdges(); i++) + { + if (swdData[i].leW < 0) + swdData[i].leW = -swdData[i].leW; + if (swdData[i].riW < 0) + swdData[i].riW = -swdData[i].riW; + + if (swdData[i].leW > 0 && swdData[i].riW <= 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW <= 0 && swdData[i].riW > 0) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + } + else if (mod == bool_op_union || mod == bool_op_diff) + { + for (int i = 0; i < numberOfEdges(); i++) + { + if (swdData[i].leW > 0 && swdData[i].riW <= 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW <= 0 && swdData[i].riW > 0) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + } + else if (mod == bool_op_inters) + { + for (int i = 0; i < numberOfEdges(); i++) + { + if (swdData[i].leW > 1 && swdData[i].riW <= 1) + { + eData[i].weight = 1; + } + else if (swdData[i].leW <= 1 && swdData[i].riW > 1) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + } else if ( mod == bool_op_cut ) { + // inverser les aretes de la coupe au besoin + for (int i=0;i<numberOfEdges();i++) { + if ( getEdge(i).st < 0 || getEdge(i).en < 0 ) { + if ( i < numberOfEdges()-1 ) { + // decaler les askForWinding + int cp = swsData[numberOfEdges()-1].firstLinkedPoint; + while (cp >= 0) { + pData[cp].askForWindingB = i; + cp = pData[cp].nextLinkedPoint; + } + } + SwapEdges(i,numberOfEdges()-1); + SubEdge(numberOfEdges()-1); +// SubEdge(i); + i--; + } else if ( ebData[i].pathID == cutPathID ) { + swdData[i].leW=swdData[i].leW%2; + swdData[i].riW=swdData[i].riW%2; + if ( swdData[i].leW < swdData[i].riW ) { + Inverse(i); + } + } + } + } else if ( mod == bool_op_slice ) { + // supprimer les aretes de la coupe + int i=numberOfEdges()-1; + for (;i>=0;i--) { + if ( ebData[i].pathID == cutPathID || getEdge(i).st < 0 || getEdge(i).en < 0 ) { + SubEdge(i); + } + } + } + else + { + for (int i = 0; i < numberOfEdges(); i++) + { + if (swdData[i].leW > 0 && swdData[i].riW <= 0) + { + eData[i].weight = 1; + } + else if (swdData[i].leW <= 0 && swdData[i].riW > 0) + { + Inverse (i); + eData[i].weight = 1; + } + else + { + eData[i].weight = 0; + SubEdge (i); + i--; + } + } + } + + delete sTree; + sTree = nullptr; + delete sEvts; + sEvts = nullptr; + + if ( mod == bool_op_cut ) { + // on garde le askForWinding + } else { + MakePointData (false); + } + MakeEdgeData (false); + MakeSweepSrcData (false); + MakeSweepDestData (false); + a->CleanupSweep (); + b->CleanupSweep (); + + if (directedEulerian(this) == false) + { +// printf( "pas euclidian2"); + _pts.clear(); + _aretes.clear(); + return shape_euler_err; + } + type = shape_polygon; + return 0; +} + +// frontend to the TesteIntersection() below +void Shape::TesteIntersection(SweepTree *t, Side s, bool onlyDiff) +{ + // get the element that is to the side s of node t + SweepTree *tt = static_cast<SweepTree*>(t->elem[s]); + if (tt == nullptr) { + return; + } + + // set left right properly, a is left, b is right + SweepTree *a = (s == LEFT) ? tt : t; + SweepTree *b = (s == LEFT) ? t : tt; + + // call the actual intersection checking function and if an intersection + // is detected, add it as an event + Geom::Point atx; + double atl; + double atr; + if (TesteIntersection(a, b, atx, atl, atr, onlyDiff)) { + sEvts->add(a, b, atx, atl, atr); + } +} + +// a crucial piece of code: computing intersections between segments +bool +Shape::TesteIntersection (SweepTree * iL, SweepTree * iR, Geom::Point &atx, double &atL, double &atR, bool onlyDiff) +{ + // get the left edge's start and end point + int lSt = iL->src->getEdge(iL->bord).st, lEn = iL->src->getEdge(iL->bord).en; + // get the right edge's start and end point + int rSt = iR->src->getEdge(iR->bord).st, rEn = iR->src->getEdge(iR->bord).en; + // get both edge vectors + Geom::Point ldir, rdir; + ldir = iL->src->eData[iL->bord].rdx; + rdir = iR->src->eData[iR->bord].rdx; + // first, a round of checks to quickly dismiss edge which obviously dont intersect, + // such as having disjoint bounding boxes + + // invert the edge vector and swap the endpoints if an edge is bottom to top + // or horizontal and right to left + if (lSt < lEn) + { + } + else + { + std::swap(lSt, lEn); + ldir = -ldir; + } + if (rSt < rEn) + { + } + else + { + std::swap(rSt, rEn); + rdir = -rdir; + } + + // these blocks check if the bounding boxes of the two don't overlap, if they + // don't overlap, we can just return false since non-overlapping bounding boxes + // indicate they won't intersect + if (iL->src->pData[lSt].rx[0] < iL->src->pData[lEn].rx[0]) + { + if (iR->src->pData[rSt].rx[0] < iR->src->pData[rEn].rx[0]) + { + if (iL->src->pData[lSt].rx[0] > iR->src->pData[rEn].rx[0]) + return false; + if (iL->src->pData[lEn].rx[0] < iR->src->pData[rSt].rx[0]) + return false; + } + else + { + if (iL->src->pData[lSt].rx[0] > iR->src->pData[rSt].rx[0]) + return false; + if (iL->src->pData[lEn].rx[0] < iR->src->pData[rEn].rx[0]) + return false; + } + } + else + { + if (iR->src->pData[rSt].rx[0] < iR->src->pData[rEn].rx[0]) + { + if (iL->src->pData[lEn].rx[0] > iR->src->pData[rEn].rx[0]) + return false; + if (iL->src->pData[lSt].rx[0] < iR->src->pData[rSt].rx[0]) + return false; + } + else + { + if (iL->src->pData[lEn].rx[0] > iR->src->pData[rSt].rx[0]) + return false; + if (iL->src->pData[lSt].rx[0] < iR->src->pData[rEn].rx[0]) + return false; + } + } + + // see the second image in the header docs of this function to visualize + // this cross product + double ang = cross (ldir, rdir); + // ang*=iL->src->eData[iL->bord].isqlength; + // ang*=iR->src->eData[iR->bord].isqlength; + if (ang <= 0) return false; // edges in opposite directions: <-left ... right -> + // they can't intersect + + // d'abord tester les bords qui partent d'un meme point + // if they come from same shape and they share the same start point + if (iL->src == iR->src && lSt == rSt) + { + // if they share the end point too, it's a doublon doesn't count as intersection + if (iL->src == iR->src && lEn == rEn) + return false; // c'est juste un doublon + // if we are here, it means they share the start point only and that counts as an interesection + // intersection point is the starting point and times are all set to -1 + atx = iL->src->pData[lSt].rx; + atR = atL = -1; + return true; // l'ordre est mauvais + } + // if they only share the end points, doesn't count as intersection (no idea why) + // in my opinion, both endpoints shouldn't count for intersection + if (iL->src == iR->src && lEn == rEn) + return false; // rien a faire=ils vont terminer au meme endroit + + // tester si on est dans une intersection multiple + + // I'm not sure what onlyDiff does but it seems it stands for "only different", which means, the intersections + // will only be detected if the two edges are coming from different shapes, if they come from the same shape, + // don't do any checks, we are not interested. but this if statement should be somewhere above in the code + // not here, shouldn't it? Why bother doing the bounding box checks? + if (onlyDiff && iL->src == iR->src) + return false; + + // on reprend les vrais points + // get the start end points again, since we might have swapped them above + lSt = iL->src->getEdge(iL->bord).st; + lEn = iL->src->getEdge(iL->bord).en; + rSt = iR->src->getEdge(iR->bord).st; + rEn = iR->src->getEdge(iR->bord).en; + + // compute intersection (if there is one) + // Boissonat anr Preparata said in one paper that double precision floats were sufficient for get single precision + // coordinates for the intersection, if the endpoints are single precision. i hope they're right... + // so till here, lSt, lEn, rSt, rEn have been reset, but note that ldir and rdir are the same + { + Geom::Point sDiff, eDiff; + double slDot, elDot; + double srDot, erDot; + // a vector from the start point of right edge to the start point of left edge + sDiff = iL->src->pData[lSt].rx - iR->src->pData[rSt].rx; + // a vector from the start point of right edge to the end point of left edge + eDiff = iL->src->pData[lEn].rx - iR->src->pData[rSt].rx; + srDot = cross(rdir, sDiff); + erDot = cross(rdir, eDiff); + // a vector from the start point of left edge to the start point of right edge + sDiff = iR->src->pData[rSt].rx - iL->src->pData[lSt].rx; + // a vector from the start point of left edge to the end point of right edge + eDiff = iR->src->pData[rEn].rx - iL->src->pData[lSt].rx; + slDot = cross(ldir, sDiff); + elDot = cross(ldir, eDiff); + // these cross products above are shown in the third picture in the header docs of this function. + // The only thing that matters to us at the moment about these cross products is their sign + // not their value, the value comes in later. I've labelled the angle arcs with the name of the + // cross product so that you can immediately visualize the sign that the cross product will take + // take your right hand, index finger to first vector, middle finger to second vector, if thumb + // is into the page, cross is positive, else it's negative. + + // basically these cross products give us a sense of where the endpoints of other vector is with + // respect to a particular vector. If both are on the opposite sides, it indicates that an intersection + // will happen. Of course you can have the edge far away and still have endpoints on opposite side, + // they won't intersect but those cases have already been ruled out above + + // if both endpoints of left edge are on one side (the same side doesn't matter which one) of the right edge + if ((srDot >= 0 && erDot >= 0) || (srDot <= 0 && erDot <= 0)) + { + // you might think okay if both endpoints of left edge are on same side of right edge, then they can't intersect + // right? no, they still can. An endpoint of the left edge can fall on the right edge and in that case + // there is an intersection + // the start point of the left edge is on the right edge + if (srDot == 0) + { + // this condition here is quite weird, due to it, some intersections won't get detected while others might, I + // don't really see why it has been done this way. See the fourth figure in the header docs of this function and + // you'll see both cases. One where the condition lSt < lEn is valid and an intersection is detected and another + // one where it isn't. In fact as I was testing this out, I realized the purpose of CheckAdjacencies. Apparently + // when a point of intersection is missed by this function, the edge still gets split up at the intersection point + // thanks to CheckAdjacencies. You can see this for yourself by taking a 1000 px by 1000 px canvas and the following + // SVG path: M 500,200 L 500,800 L 200,800 L 500,500 L 200,200 Z + // Try Path > Union with and without the CheckAdjacencies call and you'll see the difference in the resultant path. + // So I was trying to find out a case where this if statement lSt < lEn would be true and an intersection would be + // returned, I couldn't succeed at this. You can get lSt < lEn to be true, but something else above in the code will + // cause an early return, most likely the ang <= 0 condition. So in order words, I couldn't get the code below to + // run, ever. + if (lSt < lEn) + { + atx = iL->src->pData[lSt].rx; + atL = 0; + atR = slDot / (slDot - elDot); + return true; + } + else + { + return false; + } + } + else if (erDot == 0) + { + if (lSt > lEn) + { + atx = iL->src->pData[lEn].rx; + atL = 1; + atR = slDot / (slDot - elDot); + return true; + } + else + { + return false; + } + } + // This code doesn't make sense either, I couldn't get it to trigger + // TODO: Try again or just prove that it's impossible to reach here + if (srDot > 0 && erDot > 0) + { + if (rEn < rSt) + { + if (srDot < erDot) + { + if (lSt < lEn) + { + atx = iL->src->pData[lSt].rx; + atL = 0; + atR = slDot / (slDot - elDot); + return true; + } + } + else + { + if (lEn < lSt) + { + atx = iL->src->pData[lEn].rx; + atL = 1; + atR = slDot / (slDot - elDot); + return true; + } + } + } + } + if (srDot < 0 && erDot < 0) + { + if (rEn > rSt) + { + if (srDot > erDot) + { + if (lSt < lEn) + { + atx = iL->src->pData[lSt].rx; + atL = 0; + atR = slDot / (slDot - elDot); + return true; + } + } + else + { + if (lEn < lSt) + { + atx = iL->src->pData[lEn].rx; + atL = 1; + atR = slDot / (slDot - elDot); + return true; + } + } + } + } + return false; + } + // if both endpoints of the right edge are on the same side of left edge + if ((slDot >= 0 && elDot >= 0) || (slDot <= 0 && elDot <= 0)) + { + if (slDot == 0) + { + if (rSt < rEn) + { + atx = iR->src->pData[rSt].rx; + atR = 0; + atL = srDot / (srDot - erDot); + return true; + } + else + { + return false; + } + } + else if (elDot == 0) + { + if (rSt > rEn) + { + atx = iR->src->pData[rEn].rx; + atR = 1; + atL = srDot / (srDot - erDot); + return true; + } + else + { + return false; + } + } + if (slDot > 0 && elDot > 0) + { + if (lEn > lSt) + { + if (slDot < elDot) + { + if (rSt < rEn) + { + atx = iR->src->pData[rSt].rx; + atR = 0; + atL = srDot / (srDot - erDot); + return true; + } + } + else + { + if (rEn < rSt) + { + atx = iR->src->pData[rEn].rx; + atR = 1; + atL = srDot / (srDot - erDot); + return true; + } + } + } + } + if (slDot < 0 && elDot < 0) + { + if (lEn < lSt) + { + if (slDot > elDot) + { + if (rSt < rEn) + { + atx = iR->src->pData[rSt].rx; + atR = 0; + atL = srDot / (srDot - erDot); + return true; + } + } + else + { + if (rEn < rSt) + { + atx = iR->src->pData[rEn].rx; + atR = 1; + atL = srDot / (srDot - erDot); + return true; + } + } + } + } + return false; + } + + /* double slb=slDot-elDot,srb=srDot-erDot; + if ( slb < 0 ) slb=-slb; + if ( srb < 0 ) srb=-srb;*/ + // We use different formulas depending on whose sin is greater + + // These formulas would look really weird to you, but let's pick the first one + // and I'll do some maths that you can see in the header docs to elaborate what's + // happening + if (iL->src->eData[iL->bord].siEd > iR->src->eData[iR->bord].siEd) + { + atx = + (slDot * iR->src->pData[rEn].rx - + elDot * iR->src->pData[rSt].rx) / (slDot - elDot); + } + else + { + atx = + (srDot * iL->src->pData[lEn].rx - + erDot * iL->src->pData[lSt].rx) / (srDot - erDot); + } + atL = srDot / (srDot - erDot); + atR = slDot / (slDot - elDot); + return true; + } + + return true; +} + +int +Shape::PushIncidence (Shape * a, int cb, int pt, double theta) +{ + if (theta < 0 || theta > 1) + return -1; + + if (nbInc >= maxInc) + { + maxInc = 2 * nbInc + 1; + iData = + (incidenceData *) g_realloc(iData, maxInc * sizeof (incidenceData)); + } + int n = nbInc++; + iData[n].nextInc = a->swsData[cb].firstLinkedPoint; + iData[n].pt = pt; + iData[n].theta = theta; + a->swsData[cb].firstLinkedPoint = n; + return n; +} + +int +Shape::CreateIncidence (Shape * a, int no, int nPt) +{ + Geom::Point adir, diff; + adir = a->eData[no].rdx; + diff = getPoint(nPt).x - a->pData[a->getEdge(no).st].rx; + double t = dot (diff, adir); + t *= a->eData[no].ilength; + return PushIncidence (a, no, nPt, t); +} + +int +Shape::Winding (int nPt) const +{ + // the array pData has a variable named askForWindingB + // that tells us which edge to go to to find the winding number + // of the pint nPt. + int askTo = pData[nPt].askForWindingB; + if (askTo < 0 || askTo >= numberOfEdges()) // if there is no info there, just return 0 + return 0; + // if the edge is top to bottom, return left winding number otherwise the right winding number + // actually, while sweeping, ConvertToShape stores the edge on the immediate left of each point, + // hence, we are seeing the winding to the right of this edge and depending on orientation, + // right is "left" if edge is top to bottom, right is "right" if edge is bottom to top + if (getEdge(askTo).st < getEdge(askTo).en) + { + return swdData[askTo].leW; + } + else + { + return swdData[askTo].riW; + } + return 0; +} + +int +Shape::Winding (const Geom::Point px) const +{ + int lr = 0, ll = 0, rr = 0; + + // for each edge + for (int i = 0; i < numberOfEdges(); i++) + { + Geom::Point adir, diff, ast, aen; + adir = eData[i].rdx; + + ast = pData[getEdge(i).st].rx; // start point of this edge + aen = pData[getEdge(i).en].rx; // end point of this edge + + int nWeight = eData[i].weight; // weight of this edge + + // this block checks if the vertical lines crossing the start and end points of the edge + // covers the point px or not. See the first figure in the header documentation to see + // what I mean. The figure shows two contours one inside another. It shows the point px + // and the current edge that the loop is processing is drawn in black color. Two dashed + // vertical lines create a region. This block of code checks if the point px lies within + // that region or not. Because if it doesn't, we really don't care about this edge at all + // then. + if (ast[0] < aen[0]) + { + if (ast[0] > px[0]) + continue; + if (aen[0] < px[0]) + continue; + } + else + { + if (ast[0] < px[0]) + continue; + if (aen[0] > px[0]) + continue; + } + + // the situations in these blocks are explained by the second and third figure in the header documentation + // the fourth figure along with the documentation there describe what ll and rr really do + if (ast[0] == px[0]) + { + if (ast[1] >= px[1]) + continue; + if (aen[0] == px[0]) + continue; + if (aen[0] < px[0]) + ll += nWeight; + else + rr -= nWeight; + continue; + } + if (aen[0] == px[0]) + { + if (aen[1] >= px[1]) + continue; + if (ast[0] == px[0]) + continue; + if (ast[0] < px[0]) + ll -= nWeight; + else + rr += nWeight; + continue; + } + + // if the edge is below the point, it doesn't cut the ray at all + // so we don't care about it + if (ast[1] < aen[1]) + { + if (ast[1] >= px[1]) + continue; + } + else + { + if (aen[1] >= px[1]) + continue; + } + + // a vector from the edge start point to our point px whose winding we wanna calculate + diff = px - ast; + double cote = cross(adir, diff); // cross from edge vector to diff vector to figure out the orientation + if (cote == 0) + continue; + if (cote < 0) + { + if (ast[0] > px[0]) + lr += nWeight; + } + else + { + if (ast[0] < px[0]) + lr -= nWeight; + } + } + return lr + (ll + rr) / 2; // lr comes as it is, ll and rr get divided by two due to the reason I mention in the header file docs +} + +// merging duplicate points and edges +int +Shape::AssemblePoints (int st, int en) +{ + if (en > st) { + for (int i = st; i < en; i++) pData[i].oldInd = i; +// SortPoints(st,en-1); + SortPointsByOldInd (st, en - 1); // SortPointsByOldInd() is required here, because of the edges we have + // associated with the point for later computation of winding numbers. + // specifically, we need the first point we treated, it's the only one with a valid + // associated edge (man, that was a nice bug). + for (int i = st; i < en; i++) pData[pData[i].oldInd].newInd = i; + + int lastI = st; + for (int i = st; i < en; i++) { + pData[i].pending = lastI++; + if (i > st && getPoint(i - 1).x[0] == getPoint(i).x[0] && getPoint(i - 1).x[1] == getPoint(i).x[1]) { + pData[i].pending = pData[i - 1].pending; + if (pData[pData[i].pending].askForWindingS == nullptr) { + pData[pData[i].pending].askForWindingS = pData[i].askForWindingS; + pData[pData[i].pending].askForWindingB = pData[i].askForWindingB; + } else { + if (pData[pData[i].pending].askForWindingS == pData[i].askForWindingS + && pData[pData[i].pending].askForWindingB == pData[i].askForWindingB) { + // meme bord, c bon + } else { + // meme point, mais pas le meme bord: ouille! + // il faut prendre le bord le plus a gauche + // en pratique, n'arrive que si 2 maxima sont dans la meme case -> le mauvais choix prend une arete incidente + // au bon choix +// printf("doh"); + } + } + lastI--; + } else { + if (i > pData[i].pending) { + _pts[pData[i].pending].x = getPoint(i).x; + pData[pData[i].pending].rx = getPoint(i).x; + pData[pData[i].pending].askForWindingS = pData[i].askForWindingS; + pData[pData[i].pending].askForWindingB = pData[i].askForWindingB; + } + } + } + for (int i = st; i < en; i++) pData[i].newInd = pData[pData[i].newInd].pending; + return lastI; + } + return en; +} + +void +Shape::AssemblePoints (Shape * a) +{ + if (hasPoints()) + { + int lastI = AssemblePoints (0, numberOfPoints()); + + for (int i = 0; i < a->numberOfEdges(); i++) + { + a->swsData[i].stPt = pData[a->swsData[i].stPt].newInd; + a->swsData[i].enPt = pData[a->swsData[i].enPt].newInd; + } + for (int i = 0; i < nbInc; i++) + iData[i].pt = pData[iData[i].pt].newInd; + + _pts.resize(lastI); + } +} +void +Shape::AssembleAretes (FillRule directed) +{ + if ( directed == fill_justDont && _has_back_data == false ) { + directed=fill_nonZero; + } + + // for each point in points + for (int i = 0; i < numberOfPoints(); i++) { + if (getPoint(i).totalDegree() == 2) { // if simple point with one incoming edge another outgoing edge + int cb, cc; + cb = getPoint(i).incidentEdge[FIRST]; // the first edge connected to the point + cc = getPoint(i).incidentEdge[LAST]; // the last edge connected to the point + bool doublon=false; + if ((getEdge(cb).st == getEdge(cc).st && getEdge(cb).en == getEdge(cc).en) + || (getEdge(cb).st == getEdge(cc).en && getEdge(cb).en == getEdge(cc).en)) doublon=true; // if the start and end edges have same endpoints, it's a doublon edge + if ( directed == fill_justDont ) { + if ( doublon ) { + // depending on pathID, pieceID and tSt reorient cb and cc if needed + if ( ebData[cb].pathID > ebData[cc].pathID ) { + cc = getPoint(i).incidentEdge[FIRST]; // on swappe pour enlever cc + cb = getPoint(i).incidentEdge[LAST]; + } else if ( ebData[cb].pathID == ebData[cc].pathID ) { + if ( ebData[cb].pieceID > ebData[cc].pieceID ) { + cc = getPoint(i).incidentEdge[FIRST]; // on swappe pour enlever cc + cb = getPoint(i).incidentEdge[LAST]; + } else if ( ebData[cb].pieceID == ebData[cc].pieceID ) { + if ( ebData[cb].tSt > ebData[cc].tSt ) { + cc = getPoint(i).incidentEdge[FIRST]; // on swappe pour enlever cc + cb = getPoint(i).incidentEdge[LAST]; + } + } + } + } + if ( doublon ) eData[cc].weight = 0; // make cc's weight zero + } else { + } + if ( doublon ) { + if (getEdge(cb).st == getEdge(cc).st) { // if both edges share same start point + eData[cb].weight += eData[cc].weight; // you get double weight + } else { + eData[cb].weight -= eData[cc].weight; // if one's start is other's end, you subtract weight + } + eData[cc].weight = 0; // remove cc (set weight to zero) + + // winding number seed stuff + if (swsData[cc].firstLinkedPoint >= 0) { + int cp = swsData[cc].firstLinkedPoint; + while (cp >= 0) { + pData[cp].askForWindingB = cb; + cp = pData[cp].nextLinkedPoint; + } + if (swsData[cb].firstLinkedPoint < 0) { + swsData[cb].firstLinkedPoint = swsData[cc].firstLinkedPoint; + } else { + int ncp = swsData[cb].firstLinkedPoint; + while (pData[ncp].nextLinkedPoint >= 0) { + ncp = pData[ncp].nextLinkedPoint; + } + pData[ncp].nextLinkedPoint = swsData[cc].firstLinkedPoint; + } + } + + // disconnect start and end of cc + DisconnectStart (cc); + DisconnectEnd (cc); + + if (numberOfEdges() > 1) { + int cp = swsData[numberOfEdges() - 1].firstLinkedPoint; + while (cp >= 0) { + pData[cp].askForWindingB = cc; + cp = pData[cp].nextLinkedPoint; + } + } + // swap cc with last edge + SwapEdges (cc, numberOfEdges() - 1); + if (cb == numberOfEdges() - 1) { + cb = cc; + } + // pop back the last one (to completely remove it from the array) + _aretes.pop_back(); + } + } else { + int cb; + cb = getPoint(i).incidentEdge[FIRST]; + while (cb >= 0 && cb < numberOfEdges()) { + int other = Other (i, cb); + int cc; + cc = getPoint(i).incidentEdge[FIRST]; + while (cc >= 0 && cc < numberOfEdges()) { + int ncc = NextAt (i, cc); + bool doublon=false; + if (cc != cb && Other (i, cc) == other ) doublon=true; + if ( directed == fill_justDont ) { + if ( doublon ) { + if ( ebData[cb].pathID > ebData[cc].pathID ) { + doublon=false; + } else if ( ebData[cb].pathID == ebData[cc].pathID ) { + if ( ebData[cb].pieceID > ebData[cc].pieceID ) { + doublon=false; + } else if ( ebData[cb].pieceID == ebData[cc].pieceID ) { + if ( ebData[cb].tSt > ebData[cc].tSt ) { + doublon=false; + } + } + } + } + if ( doublon ) eData[cc].weight = 0; + } else { + } + if ( doublon ) { +// if (cc != cb && Other (i, cc) == other) { + // doublon + if (getEdge(cb).st == getEdge(cc).st) { + eData[cb].weight += eData[cc].weight; + } else { + eData[cb].weight -= eData[cc].weight; + } + eData[cc].weight = 0; + + if (swsData[cc].firstLinkedPoint >= 0) { + int cp = swsData[cc].firstLinkedPoint; + while (cp >= 0) { + pData[cp].askForWindingB = cb; + cp = pData[cp].nextLinkedPoint; + } + if (swsData[cb].firstLinkedPoint < 0) { + swsData[cb].firstLinkedPoint = swsData[cc].firstLinkedPoint; + } else { + int ncp = swsData[cb].firstLinkedPoint; + while (pData[ncp].nextLinkedPoint >= 0) { + ncp = pData[ncp].nextLinkedPoint; + } + pData[ncp].nextLinkedPoint = swsData[cc].firstLinkedPoint; + } + } + + DisconnectStart (cc); + DisconnectEnd (cc); + if (numberOfEdges() > 1) { + int cp = swsData[numberOfEdges() - 1].firstLinkedPoint; + while (cp >= 0) { + pData[cp].askForWindingB = cc; + cp = pData[cp].nextLinkedPoint; + } + } + SwapEdges (cc, numberOfEdges() - 1); + if (cb == numberOfEdges() - 1) { + cb = cc; + } + if (ncc == numberOfEdges() - 1) { + ncc = cc; + } + _aretes.pop_back(); + } + cc = ncc; + } + cb = NextAt (i, cb); + } + } + } + + if ( directed == fill_justDont ) { + for (int i = 0; i < numberOfEdges(); i++) { + if (eData[i].weight == 0) { + // SubEdge(i); + // i--; + } else { + if (eData[i].weight < 0) Inverse (i); + } + } + } else { + for (int i = 0; i < numberOfEdges(); i++) { + if (eData[i].weight == 0) { + // SubEdge(i); + // i--; + } else { + if (eData[i].weight < 0) Inverse (i); + } + } + } + } +void +Shape::GetWindings (Shape * /*a*/, Shape * /*b*/, BooleanOp /*mod*/, bool brutal) +{ + // preparation du parcours + for (int i = 0; i < numberOfEdges(); i++) + { + swdData[i].misc = nullptr; + swdData[i].precParc = swdData[i].suivParc = -1; + } + + // we make sure that the edges are sorted. What this means is that for each point, all + // the edges that are attached to it are arranged in the linked list according to + // clockwise order (of their spatial orientation) + // chainage + SortEdges (); + + int searchInd = 0; + + int lastPtUsed = 0; + // okay now let's see what this outer most loop is supposed to do. Look, you can have a directed graph + // with multiple paths that don't touch each other. For example a rectangle inside another rectangle. + // If you just start at the first point and follow the edges moving around, you'd have explored one + // sub-graph but you wouldn't even touch the others. This outer loop ensures that all the points have + // been walked over. We start at the first point and start exploring. When we reach the end of that + // sub-graph, we update lastPtUsed and this outerloop will check if there are still points remaining + // to be explored, if yes, we start with the first point (that we haven't touched yet) + do + { + int startBord = -1; + int outsideW = 0; // the winding number outside (to the top left) of the first point in a sub-graph + { + int fi = 0; + // ignore all points that don't have any edges attached + for (fi = lastPtUsed; fi < numberOfPoints(); fi++) + { + if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == nullptr) + break; + } + lastPtUsed = fi + 1; + if (fi < numberOfPoints()) + { + // get the first edge attached to the first point + int bestB = getPoint(fi).incidentEdge[FIRST]; + if (bestB >= 0) + { + // let's start with this edge + startBord = bestB; + // is the first point the first in the array? if yes, this ensure it's at the top most and left most position. + // Hence the winding number must be zero (since that region is literally outside everything) + if (fi == 0) + { + outsideW = 0; + } + else + { + // you can either compute the winding number by iterating through all the edges + // basically that would work by seeing how many edges a ray from (0, +infty) would cross + // and in which order + if (brutal) + { + outsideW = Winding (getPoint(fi).x); + } + // or we can get the winding number for that point computed by the sweepline.. this is pretty + // interesting. + else + { + outsideW = Winding (fi); + } + } + // TODO: Look at this piece + if ( getPoint(fi).totalDegree() == 1 ) { + if ( fi == getEdge(startBord).en ) { + if ( eData[startBord].weight == 0 ) { + // on se contente d'inverser + Inverse(startBord); + } else { + // on passe le askForWinding (sinon ca va rester startBord) + pData[getEdge(startBord).st].askForWindingB=pData[getEdge(startBord).en].askForWindingB; + } + } + } + if (getEdge(startBord).en == fi) + outsideW += eData[startBord].weight; + } + } + } + if (startBord >= 0) + { + // now start from this edge + // parcours en profondeur pour mettre les leF et riF a leurs valeurs + swdData[startBord].misc = (void *) 1; + // setting the winding numbers for this edge + // one question I had was, would these values for the first edge will always be valid? + // The answer is yes. Due to the fact that edges are sorted clockwise, and that + // we start with the top most (and leftmost if mutliple top most points exist), and that + // there is a piece of code above that adds weight to start edge if the edge ends at the current + // point, I think these will always be correct values. + swdData[startBord].leW = outsideW; + swdData[startBord].riW = outsideW - eData[startBord].weight; +// if ( doDebug ) printf("part de %d\n",startBord); + // curBord is the current edge that we are at + int curBord = startBord; + // curDir is the direction, true means we are going in the direction of the edge vector, false means + // we are going in the direction opposite to the edge vector + bool curDir = true; + swdData[curBord].precParc = -1; + swdData[curBord].suivParc = -1; + // the depth first search + do + { + int cPt; + // if curDir is true, we are going along the edge, so get the end point + if (curDir) + cPt = getEdge(curBord).en; + else // if curDir is false, we are going opposite to the edge, so get the start point + cPt = getEdge(curBord).st; + + // start finding the next edge to move to + int nb = curBord; +// if ( doDebug ) printf("de curBord= %d avec leF= %d et riF= %d -> ",curBord,swdData[curBord].leW,swdData[curBord].riW); + do + { + int nnb = -1; + // see the diagram attached in the header file documentation of this function to see the + // four situations that can come up. + // outsideW here does not mean outside winding, in fact it means inside winding. + // if we are going along the edge, we save the right winding number for later use + if (getEdge(nb).en == cPt) + { + outsideW = swdData[nb].riW; + nnb = CyclePrevAt (cPt, nb); // get the prev edge, since sorting was clockwise, this means get the first counter-clockwise edge + } + // if we are going against the edge, we save it's left winding number for later use + else + { + outsideW = swdData[nb].leW; + nnb = CyclePrevAt (cPt, nb); + } + if (nnb == nb) // if we didn't get any "new" edge + { + // cul-de-sac + nb = -1; + break; + } + nb = nnb; + } // you can break for three reasons from this loop: having no other edge, we got the same one we started on, edge hasn't been visited yet + while (nb >= 0 && nb != curBord && swdData[nb].misc != nullptr); + // in the beginning, you'd break from the upper loop due to the misc condition and later on, you'll break due to nb != curBord which + // means we need to start backtracking + if (nb < 0 || nb == curBord) // backtracking block + { + // retour en arriere + // so if we are here, we couldn't get any new edge that we haven't seen yet + int oPt; + // we wanna find the previous point (since we are going back) + // if curDir is True, we were going along the edge, so get the start point (going backwards u see) + if (curDir) + oPt = getEdge(curBord).st; + else // if curDir is false, we were going against edge, so get the end point (going backwards) + oPt = getEdge(curBord).en; + curBord = swdData[curBord].precParc; // make current edge the previous one in traversal (back tracking) +// if ( doDebug ) printf("retour vers %d\n",curBord); + if (curBord < 0) // if no edge to go back to, break + break; + if (oPt == getEdge(curBord).en) // if this new "current edge" ends at that point, curDir should be true, since we ideally have to go forward + curDir = true; + else // otherwise set it to false, so ideal direction would be against edge + curDir = false; + // I say ideal because this this is how backtracking is, if you have nothing new to go forward to, you go back once and see + // if there is another new edge to go forward to, if not you go back again, and you keep doing this until the point comes where + // you have nothing to go back to and then you break from the loop + } + else // okay we have new edge to compute windings + { + swdData[nb].misc = (void *) 1; // we visited this edge, so mark that + swdData[nb].ind = searchInd++; // probably for use later on? + if (cPt == getEdge(nb).st) // this outsideW is the winding stored before, see the diagram in header file + { + swdData[nb].riW = outsideW; + swdData[nb].leW = outsideW + eData[nb].weight; + } + else + { + swdData[nb].leW = outsideW; + swdData[nb].riW = outsideW - eData[nb].weight; + } + // maintaining the stack of traversal + swdData[nb].precParc = curBord; + swdData[curBord].suivParc = nb; + // this edge becomes current edge now + curBord = nb; +// if ( doDebug ) printf("suite %d\n",curBord); + // set direction depending on how this edge is oriented + if (cPt == getEdge(nb).en) + curDir = false; + else + curDir = true; + } + } + while (true /*swdData[curBord].precParc >= 0 */ ); + // fin du cas non-oriente + } + } + while (lastPtUsed < numberOfPoints()); +// fflush(stdout); +} + +bool +Shape::TesteIntersection (Shape * ils, Shape * irs, int ilb, int irb, + Geom::Point &atx, double &atL, double &atR, + bool /*onlyDiff*/) +{ + int lSt = ils->getEdge(ilb).st, lEn = ils->getEdge(ilb).en; + int rSt = irs->getEdge(irb).st, rEn = irs->getEdge(irb).en; + if (lSt == rSt || lSt == rEn) + { + return false; + } + if (lEn == rSt || lEn == rEn) + { + return false; + } + + Geom::Point ldir, rdir; + ldir = ils->eData[ilb].rdx; + rdir = irs->eData[irb].rdx; + + double il = ils->pData[lSt].rx[0], it = ils->pData[lSt].rx[1], ir = + ils->pData[lEn].rx[0], ib = ils->pData[lEn].rx[1]; + if (il > ir) + { + std::swap(il, ir); + } + if (it > ib) + { + std::swap(it, ib); + } + double jl = irs->pData[rSt].rx[0], jt = irs->pData[rSt].rx[1], jr = + irs->pData[rEn].rx[0], jb = irs->pData[rEn].rx[1]; + if (jl > jr) + { + std::swap(jl, jr); + } + if (jt > jb) + { + std::swap(jt, jb); + } + + if (il > jr || it > jb || ir < jl || ib < jt) + return false; + + // pre-test + { + Geom::Point sDiff, eDiff; + double slDot, elDot; + double srDot, erDot; + sDiff = ils->pData[lSt].rx - irs->pData[rSt].rx; + eDiff = ils->pData[lEn].rx - irs->pData[rSt].rx; + srDot = cross(rdir, sDiff); + erDot = cross(rdir, eDiff); + if ((srDot >= 0 && erDot >= 0) || (srDot <= 0 && erDot <= 0)) + return false; + + sDiff = irs->pData[rSt].rx - ils->pData[lSt].rx; + eDiff = irs->pData[rEn].rx - ils->pData[lSt].rx; + slDot = cross(ldir, sDiff); + elDot = cross(ldir, eDiff); + if ((slDot >= 0 && elDot >= 0) || (slDot <= 0 && elDot <= 0)) + return false; + + double slb = slDot - elDot, srb = srDot - erDot; + if (slb < 0) + slb = -slb; + if (srb < 0) + srb = -srb; + if (slb > srb) + { + atx = + (slDot * irs->pData[rEn].rx - elDot * irs->pData[rSt].rx) / (slDot - + elDot); + } + else + { + atx = + (srDot * ils->pData[lEn].rx - erDot * ils->pData[lSt].rx) / (srDot - + erDot); + } + atL = srDot / (srDot - erDot); + atR = slDot / (slDot - elDot); + return true; + } + + // a mettre en double precision pour des resultats exacts + Geom::Point usvs; + usvs = irs->pData[rSt].rx - ils->pData[lSt].rx; + + // pas sur de l'ordre des coefs de m + Geom::Affine m(ldir[0], ldir[1], + rdir[0], rdir[1], + 0, 0); + double det = m.det(); + + double tdet = det * ils->eData[ilb].isqlength * irs->eData[irb].isqlength; + + if (tdet > -0.0001 && tdet < 0.0001) + { // ces couillons de vecteurs sont colineaires + Geom::Point sDiff, eDiff; + double sDot, eDot; + sDiff = ils->pData[lSt].rx - irs->pData[rSt].rx; + eDiff = ils->pData[lEn].rx - irs->pData[rSt].rx; + sDot = cross(rdir, sDiff); + eDot = cross(rdir, eDiff); + + atx = + (sDot * irs->pData[lEn].rx - eDot * irs->pData[lSt].rx) / (sDot - + eDot); + atL = sDot / (sDot - eDot); + + sDiff = irs->pData[rSt].rx - ils->pData[lSt].rx; + eDiff = irs->pData[rEn].rx - ils->pData[lSt].rx; + sDot = cross(ldir, sDiff); + eDot = cross(ldir, eDiff); + + atR = sDot / (sDot - eDot); + + return true; + } + + // plus de colinearite ni d'extremites en commun + m[1] = -m[1]; + m[2] = -m[2]; + { + double swap = m[0]; + m[0] = m[3]; + m[3] = swap; + } + + atL = (m[0]* usvs[0] + m[1] * usvs[1]) / det; + atR = -(m[2] * usvs[0] + m[3] * usvs[1]) / det; + atx = ils->pData[lSt].rx + atL * ldir; + + + return true; +} + +bool +Shape::TesteAdjacency (Shape * a, int no, const Geom::Point atx, int nPt, + bool push) +{ + if (nPt == a->swsData[no].stPt || nPt == a->swsData[no].enPt) + return false; + + Geom::Point adir, diff, ast, aen, diff1, diff2, diff3, diff4; + + ast = a->pData[a->getEdge(no).st].rx; + aen = a->pData[a->getEdge(no).en].rx; + + adir = a->eData[no].rdx; + + double sle = a->eData[no].length; + double ile = a->eData[no].ilength; + + diff = atx - ast; + + double e = IHalfRound(cross(adir, diff) * a->eData[no].isqlength); + if (-3 < e && e < 3) + { + double rad = HalfRound (0.501); // when using single precision, 0.505 is better (0.5 would be the correct value, + // but it produces lots of bugs) + diff1[0] = diff[0] - rad; + diff1[1] = diff[1] - rad; + diff2[0] = diff[0] + rad; + diff2[1] = diff[1] - rad; + diff3[0] = diff[0] + rad; + diff3[1] = diff[1] + rad; + diff4[0] = diff[0] - rad; + diff4[1] = diff[1] + rad; + double di1, di2; + bool adjacent = false; + di1 = cross(adir, diff1); + di2 = cross(adir, diff3); + if ((di1 < 0 && di2 > 0) || (di1 > 0 && di2 < 0)) + { + adjacent = true; + } + else + { + di1 = cross(adir, diff2); + di2 = cross(adir, diff4); + if ((di1 < 0 && di2 > 0) || (di1 > 0 && di2 < 0)) + { + adjacent = true; + } + } + if (adjacent) + { + double t = dot (diff, adir); + if (t > 0 && t < sle) + { + if (push) + { + t *= ile; + PushIncidence (a, no, nPt, t); + } + return true; + } + } + } + return false; +} + +void +Shape::CheckAdjacencies (int lastPointNo, int lastChgtPt, Shape * /*shapeHead*/, + int /*edgeHead*/) +{ + // for each event in chgts + for (auto & chgt : chgts) + { + // get the chgt.ptoNo, which is the point at which the event happened + int chLeN = chgt.ptNo; + int chRiN = chgt.ptNo; + if (chgt.src) + { + Shape *lS = chgt.src; + int lB = chgt.bord; + // get the leftRnd and rightRnd of this edge + int lftN = lS->swsData[lB].leftRnd; + int rgtN = lS->swsData[lB].rightRnd; + // expand the range chLeN..chRiN + if (lftN < chLeN) + chLeN = lftN; + if (rgtN > chRiN) + chRiN = rgtN; + // check each point from lastChgtPt to leftN-1 for a possible adjacency with + // the edge, if detected mark it by modifying leftRnd + // Note we do this in reverse order by starting at the point closer to the + // edge first. If an adjacency is not detected, we immediately break since + // if a point is not adjacent, another on its left won't be adjacent either +// for (int n=lftN;n<=rgtN;n++) CreateIncidence(lS,lB,n); + for (int n = lftN - 1; n >= lastChgtPt; n--) + { + if (TesteAdjacency (lS, lB, getPoint(n).x, n, false) == + false) + break; + lS->swsData[lB].leftRnd = n; + } + // check each point from rgtN+1 to lastPointNo (not included) for a possible adjacency with + // the edge, if detected mark it by modifying rightRnd + // If an adjacency is not detected, we immediately break since if a point + // is not adjacent, another one to its right won't be either. + for (int n = rgtN + 1; n < lastPointNo; n++) + { + if (TesteAdjacency (lS, lB, getPoint(n).x, n, false) == + false) + break; + lS->swsData[lB].rightRnd = n; + } + } + // totally identical to the block above + if (chgt.osrc) + { + Shape *rS = chgt.osrc; + int rB = chgt.obord; + int lftN = rS->swsData[rB].leftRnd; + int rgtN = rS->swsData[rB].rightRnd; + if (lftN < chLeN) + chLeN = lftN; + if (rgtN > chRiN) + chRiN = rgtN; +// for (int n=lftN;n<=rgtN;n++) CreateIncidence(rS,rB,n); + for (int n = lftN - 1; n >= lastChgtPt; n--) + { + if (TesteAdjacency (rS, rB, getPoint(n).x, n, false) == + false) + break; + rS->swsData[rB].leftRnd = n; + } + for (int n = rgtN + 1; n < lastPointNo; n++) + { + if (TesteAdjacency (rS, rB, getPoint(n).x, n, false) == + false) + break; + rS->swsData[rB].rightRnd = n; + } + } + // very interesting part, deals with edges to the left in the sweepline at the time + // the event took place + if (chgt.lSrc) + { + // is the left edge's leftRnd smaller than lastChgtPt, basically this is a way to check + // if leftRnd got updated in the previous iteration of the main loop of Shape::ConvertToShape + // or not. + if (chgt.lSrc->swsData[chgt.lBrd].leftRnd < lastChgtPt) + { + // get the left edge and its shape + Shape *nSrc = chgt.lSrc; + int nBrd = chgt.lBrd /*,nNo=chgts[cCh].ptNo */ ; + bool hit; + + // iterate through the linked list of edges to the left + do + { + hit = false; // adjacency got detected? + // check all points from chRiN to chLeN + // we go right to left + for (int n = chRiN; n >= chLeN; n--) + { + // test if the point is adjacent to the edge + if (TesteAdjacency + (nSrc, nBrd, getPoint(n).x, n, false)) + { + // has the leftRnd been updated in previous iteration? if no? set it directly + if (nSrc->swsData[nBrd].leftRnd < lastChgtPt) + { + nSrc->swsData[nBrd].leftRnd = n; + nSrc->swsData[nBrd].rightRnd = n; + } + else // if yes, we do some checking and only update if it expands the span + { + if (n < nSrc->swsData[nBrd].leftRnd) + nSrc->swsData[nBrd].leftRnd = n; + if (n > nSrc->swsData[nBrd].rightRnd) + nSrc->swsData[nBrd].rightRnd = n; + } + hit = true; + } + } + // test all points between lastChgtPt and chLeN - 1 + for (int n = chLeN - 1; n >= lastChgtPt; n--) + { + if (TesteAdjacency + (nSrc, nBrd, getPoint(n).x, n, false) == false) + break; + if (nSrc->swsData[nBrd].leftRnd < lastChgtPt) + { + nSrc->swsData[nBrd].leftRnd = n; + nSrc->swsData[nBrd].rightRnd = n; + } + else + { + if (n < nSrc->swsData[nBrd].leftRnd) + nSrc->swsData[nBrd].leftRnd = n; + if (n > nSrc->swsData[nBrd].rightRnd) + nSrc->swsData[nBrd].rightRnd = n; + } + hit = true; + } + // if no adjacency got detected, no point in going further left so break, if yes + // we continue and see if we can repeat the process on the edge to the left + // and so on (basically going left detecting adjacencies) + if (hit) + { + // get the edge on the left, if non exist, break + SweepTree *node = + static_cast < SweepTree * >(nSrc->swsData[nBrd].misc); + if (node == nullptr) + break; + node = static_cast < SweepTree * >(node->elem[LEFT]); + if (node == nullptr) + break; + nSrc = node->src; + nBrd = node->bord; + // the edge on the left, did its leftRnd update in the previous iteration of the main loop if ConvertToShape? + if (nSrc->swsData[nBrd].leftRnd >= lastChgtPt) + break; + } + } + while (hit); + + } + } + // same thing but to the right side? + if (chgt.rSrc) + { + if (chgt.rSrc->swsData[chgt.rBrd].leftRnd < lastChgtPt) + { + Shape *nSrc = chgt.rSrc; + int nBrd = chgt.rBrd /*,nNo=chgts[cCh].ptNo */ ; + bool hit; + do + { + hit = false; + // test points between chLeN and chRiN for adjacency + // but go left to right + for (int n = chLeN; n <= chRiN; n++) + { + if (TesteAdjacency + (nSrc, nBrd, getPoint(n).x, n, false)) + { + if (nSrc->swsData[nBrd].leftRnd < lastChgtPt) + { + nSrc->swsData[nBrd].leftRnd = n; + nSrc->swsData[nBrd].rightRnd = n; + } + else + { + if (n < nSrc->swsData[nBrd].leftRnd) + nSrc->swsData[nBrd].leftRnd = n; + if (n > nSrc->swsData[nBrd].rightRnd) + nSrc->swsData[nBrd].rightRnd = n; + } + hit = true; + } + } + // testing points between chRiN and lastPointNo for adjacency + for (int n = chRiN + 1; n < lastPointNo; n++) + { + if (TesteAdjacency + (nSrc, nBrd, getPoint(n).x, n, false) == false) + break; + if (nSrc->swsData[nBrd].leftRnd < lastChgtPt) + { + nSrc->swsData[nBrd].leftRnd = n; + nSrc->swsData[nBrd].rightRnd = n; + } + else + { + if (n < nSrc->swsData[nBrd].leftRnd) + nSrc->swsData[nBrd].leftRnd = n; + if (n > nSrc->swsData[nBrd].rightRnd) + nSrc->swsData[nBrd].rightRnd = n; + } + hit = true; + } + if (hit) + { + SweepTree *node = + static_cast < SweepTree * >(nSrc->swsData[nBrd].misc); + if (node == nullptr) + break; + node = static_cast < SweepTree * >(node->elem[RIGHT]); + if (node == nullptr) + break; + nSrc = node->src; + nBrd = node->bord; + if (nSrc->swsData[nBrd].leftRnd >= lastChgtPt) + break; + } + } + while (hit); + } + } + } +} + + +void Shape::AddChgt(int lastPointNo, int lastChgtPt, Shape * &shapeHead, + int &edgeHead, sTreeChangeType type, Shape * lS, int lB, Shape * rS, + int rB) +{ + // fill in the event details and push it + sTreeChange c; + c.ptNo = lastPointNo; + c.type = type; + c.src = lS; + c.bord = lB; + c.osrc = rS; + c.obord = rB; + chgts.push_back(c); + // index of the newly added event + const int nCh = chgts.size() - 1; + + /* FIXME: this looks like a cut and paste job */ + + if (lS) { + // if there is an edge to the left, mark it in lSrc + SweepTree *lE = static_cast < SweepTree * >(lS->swsData[lB].misc); + if (lE && lE->elem[LEFT]) { + SweepTree *llE = static_cast < SweepTree * >(lE->elem[LEFT]); + chgts[nCh].lSrc = llE->src; + chgts[nCh].lBrd = llE->bord; + } else { + chgts[nCh].lSrc = nullptr; + chgts[nCh].lBrd = -1; + } + + // lastChgtPt is same as lastPointNo if lastPointNo is the leftmost point in that y level and it's the + // left most point on the y level of lastPointNo otherwise + // leftRnd will be smaller than lastChgtPt if the last time the edge participated in any event (edge addition/intersection) + // was before lastChgtPt. + if (lS->swsData[lB].leftRnd < lastChgtPt) { + lS->swsData[lB].leftRnd = lastPointNo; // set leftRnd to the current point + lS->swsData[lB].nextSh = shapeHead; // add this edge in the linked list + lS->swsData[lB].nextBo = edgeHead; + edgeHead = lB; + shapeHead = lS; + } else { // If an event occured to the edge after lastChgtPt (which is possible, for example, a horizontal edge + // that intersects with other edges. + // get the leftRnd already + int old = lS->swsData[lB].leftRnd; + // This seems really weird. I suspect this if statement will never be true in a top to bottom sweepline + // see if we reached this point, it means old >= lastChgtPt + // and lastChgtPt is the leftmost point at the current y level of lastPointNo + // so how can old have an x greater than lastPoint? The sweepline hasn't reached that position yet! + if (getPoint(old).x[0] > getPoint(lastPointNo).x[0]) { + lS->swsData[lB].leftRnd = lastPointNo; + } + } + // same logic as in the upper block + if (lS->swsData[lB].rightRnd < lastChgtPt) { + lS->swsData[lB].rightRnd = lastPointNo; + } else { + int old = lS->swsData[lB].rightRnd; + if (getPoint(old).x[0] < getPoint(lastPointNo).x[0]) + lS->swsData[lB].rightRnd = lastPointNo; + } + } + + // it's an intersection event and rS is the right edge's shape and rB is the + // right edge + if (rS) { + // get the edge on the right and set it in rBrd and rSrc + SweepTree *rE = static_cast < SweepTree * >(rS->swsData[rB].misc); + if (rE->elem[RIGHT]) { + SweepTree *rrE = static_cast < SweepTree * >(rE->elem[RIGHT]); + chgts[nCh].rSrc = rrE->src; + chgts[nCh].rBrd = rrE->bord; + } else { + chgts[nCh].rSrc = nullptr; + chgts[nCh].rBrd = -1; + } + + // same code as above, except that it's on rS + if (rS->swsData[rB].leftRnd < lastChgtPt) { + rS->swsData[rB].leftRnd = lastPointNo; + rS->swsData[rB].nextSh = shapeHead; + rS->swsData[rB].nextBo = edgeHead; + edgeHead = rB; + shapeHead = rS; + } else { + int old = rS->swsData[rB].leftRnd; + if (getPoint(old).x[0] > getPoint(lastPointNo).x[0]) { + rS->swsData[rB].leftRnd = lastPointNo; + } + } + if (rS->swsData[rB].rightRnd < lastChgtPt) { + rS->swsData[rB].rightRnd = lastPointNo; + } else { + int old = rS->swsData[rB].rightRnd; + if (getPoint(old).x[0] < getPoint(lastPointNo).x[0]) + rS->swsData[rB].rightRnd = lastPointNo; + } + } else { // if rS wasn't set, this is not an intersection event, so + // check if there is an edge to the right and set rBrd + SweepTree *lE = static_cast < SweepTree * >(lS->swsData[lB].misc); + if (lE && lE->elem[RIGHT]) { + SweepTree *rlE = static_cast < SweepTree * >(lE->elem[RIGHT]); + chgts[nCh].rSrc = rlE->src; + chgts[nCh].rBrd = rlE->bord; + } else { + chgts[nCh].rSrc = nullptr; + chgts[nCh].rBrd = -1; + } + } +} + +// is this a debug function? It's calling localized "printf" ... +void +Shape::Validate () +{ + for (int i = 0; i < numberOfPoints(); i++) + { + pData[i].rx = getPoint(i).x; + } + for (int i = 0; i < numberOfEdges(); i++) + { + eData[i].rdx = getEdge(i).dx; + } + for (int i = 0; i < numberOfEdges(); i++) + { + for (int j = i + 1; j < numberOfEdges(); j++) + { + Geom::Point atx; + double atL, atR; + if (TesteIntersection (this, this, i, j, atx, atL, atR, false)) + { + printf ("%i %i %f %f di=%f %f dj=%f %f\n", i, j, atx[0],atx[1],getEdge(i).dx[0],getEdge(i).dx[1],getEdge(j).dx[0],getEdge(j).dx[1]); + } + } + } + fflush (stdout); +} + +void +Shape::CheckEdges (int lastPointNo, int lastChgtPt, Shape * a, Shape * b, + BooleanOp mod) +{ + + for (auto & chgt : chgts) + { + // if an edge addition event, we want to set curPoint to the point at which + // the edge was added. chgt.ptNo is automatically updated after any possible sorting and merging + // so thats good too :-) + if (chgt.type == 0) + { + Shape *lS = chgt.src; + int lB = chgt.bord; + lS->swsData[lB].curPoint = chgt.ptNo; + } + } + // for each event in events + for (auto & chgt : chgts) + { +// int chLeN=chgts[cCh].ptNo; +// int chRiN=chgts[cCh].ptNo; + // do the main edge (by do I mean process it, see if anything needs to be drawn, if yes, draw it) + if (chgt.src) + { + Shape *lS = chgt.src; + int lB = chgt.bord; + Avance (lastPointNo, lastChgtPt, lS, lB, a, b, mod); + } + // do the other edge (the right in intersection, wont exist if chgt wasn't an intersection event) + if (chgt.osrc) + { + Shape *rS = chgt.osrc; + int rB = chgt.obord; + Avance (lastPointNo, lastChgtPt, rS, rB, a, b, mod); + } + + // See there are few cases due to which an edge will have a leftRnd >= lastChgtPt. Either the + // edge had some event associated with it (addition/removal/intersection) at that y level. Or + // there was some point in the previous y level that was on top of the edge, and thus an + // adjacency was detected and the leftRnd/rightRnd were set accordingly. If you have neither of + // these, leftRnd/rightRnd won't be set at all. If the case is former, that the edge had an + // event at the previous y level, the blocks above will automatically call Avance on the edge. + // However, for the latter, we have no chgt event associated with the edge. Thus, the blocks + // below calls Shape::Avance on edges to the left and to the right of the unique (or the left) + // edge. However, it's called only if leftRnd >= lastChgtPt. + if (chgt.lSrc) + { + Shape *nSrc = chgt.lSrc; + int nBrd = chgt.lBrd; + while (nSrc->swsData[nBrd].leftRnd >= // <-- if yes, means some event occured to this event after lastChgtPt or an adjacency was detected + lastChgtPt /*&& nSrc->swsData[nBrd].doneTo < lastChgtPt */ ) + { + Avance (lastPointNo, lastChgtPt, nSrc, nBrd, a, b, mod); + + SweepTree *node = + static_cast < SweepTree * >(nSrc->swsData[nBrd].misc); + if (node == nullptr) + break; + node = static_cast < SweepTree * >(node->elem[LEFT]); + if (node == nullptr) + break; + nSrc = node->src; + nBrd = node->bord; + } + } + if (chgt.rSrc) + { + Shape *nSrc = chgt.rSrc; + int nBrd = chgt.rBrd; + while (nSrc->swsData[nBrd].rightRnd >= + lastChgtPt /*&& nSrc->swsData[nBrd].doneTo < lastChgtPt */ ) + { + Avance (lastPointNo, lastChgtPt, nSrc, nBrd, a, b, mod); + + SweepTree *node = + static_cast < SweepTree * >(nSrc->swsData[nBrd].misc); + if (node == nullptr) + break; + node = static_cast < SweepTree * >(node->elem[RIGHT]); + if (node == nullptr) + break; + nSrc = node->src; + nBrd = node->bord; + } + } + } +} + +void +Shape::Avance (int lastPointNo, int lastChgtPt, Shape * lS, int lB, Shape * /*a*/, + Shape * b, BooleanOp mod) +{ + double dd = HalfRound (1); // get the smallest step you can take in the rounding grid. + bool avoidDiag = false; // triggers some special diagonal avoiding code below +// if ( lastChgtPt > 0 && pts[lastChgtPt-1].y+dd == pts[lastChgtPt].y ) avoidDiag=true; + + // my best guess is that direct acts kinda as an inverter. If set to true, edges are drawn + // in the direction they should be drawn in, if set to false, they are inverted. This is needed + // if the edge is coming from shape b and the mod is bool_op_diff or bool_op_symdiff. This is how + // Livarot does these boolean operations. + bool direct = true; + if (lS == b && (mod == bool_op_diff || mod == bool_op_symdiff)) + direct = false; + // get the leftRnd and rightRnd points of the edge lB. For most edges, leftRnd and rightRnd are identical + // however when you have a horizontal edge, they can be different. + int lftN = lS->swsData[lB].leftRnd; + int rgtN = lS->swsData[lB].rightRnd; + // doneTo acts as a marker. Once Avance processes an edge at a certain y level, it sets doneTo to the + // right most point at that y level. See the end of this function to see how it does this. + if (lS->swsData[lB].doneTo < lastChgtPt) + { + // the last point till which this edge has been drawn + int lp = lS->swsData[lB].curPoint; + // if the last point exists and lastChgtPt.y is just dd (1 rounded step) bigger than lastpoint.y + // in simpler words, the last point drawn is just 1 rounded step above lastChgtPt + // Look, if there is a potential edge to draw, that edge is going to have its upper endpoint at lp + // and could have the lower endpoint lftN...rgtN whatever, but we are sure that it can't go any lower (downwards) + // than lastChgtPt. If this "if" block evalues to true, there is a possibility we might an edge that would + // be diagonal for example 1 rounded unit dd down and to the right. We would like to avoid this if there is + // a point right below, so we can draw two edges, first down and then right. See the figure in the header + // docs to see what I mean + if (lp >= 0 && getPoint(lp).x[1] + dd == getPoint(lastChgtPt).x[1]) + avoidDiag = true; + // if the edge is horizontal + if (lS->eData[lB].rdx[1] == 0) + { + // tjs de gauche a droite et pas de diagonale -- > left to right horizonal edge won't be diagonal (since it's horizontal :P) + if (lS->eData[lB].rdx[0] >= 0) // edge is left to right + { + for (int p = lftN; p <= rgtN; p++) + { + DoEdgeTo (lS, lB, p, direct, true); + lp = p; + } + } + else // edge is right to left + { + for (int p = lftN; p <= rgtN; p++) + { + DoEdgeTo (lS, lB, p, direct, false); + lp = p; + } + } + } + else if (lS->eData[lB].rdx[1] > 0) // the edge is top to bottom + { + if (lS->eData[lB].rdx[0] >= 0) // edge is top to bottom and left to right + { + + for (int p = lftN; p <= rgtN; p++) // for the range lftN..rgtN + { + // if avoidDiag is true, point p is lftN and the point the edge is to be drawn to (lftN) has an x that's + // 1 rounded unit (dd) greater than the last point drawn. So basically lftN is one unit down and one unit + // to the right of lp + if (avoidDiag && p == lftN && getPoint(lftN).x[0] == getPoint(lp).x[0] + dd) + { + // we would want to avoid the diagonal but only if there is a point (in our directed graph) + // just to the left of the original lower endpoint and instead of doing a diagonal, we can create an edge to that point + // and then from that point to the original endpoint. see the figure in the header docs to see what I mean + if (lftN > 0 && lftN - 1 >= lastChgtPt // if there is a point in the figure just below lp and to the left of lftN + && getPoint(lftN - 1).x[0] == getPoint(lp).x[0]) // that point has x equal to that of lp (right below it) + { + DoEdgeTo (lS, lB, lftN - 1, direct, true); // draw an edge to lftn - 1 + DoEdgeTo (lS, lB, lftN, direct, true); // then draw an edge to lftN + } + else + { + DoEdgeTo (lS, lB, lftN, direct, true); + } + } + else + { + DoEdgeTo (lS, lB, p, direct, true); + } + lp = p; + } + } + else + { + + for (int p = rgtN; p >= lftN; p--) // top to bottom and right to left + { + // exactly identical to the code above except that it's a diagonal down and to the left + if (avoidDiag && p == rgtN && getPoint(rgtN).x[0] == getPoint(lp).x[0] - dd) + { + if (rgtN < numberOfPoints() && rgtN + 1 < lastPointNo // refer to first diagram in header docs to see why this condition + && getPoint(rgtN + 1).x[0] == getPoint(lp).x[0]) + { + DoEdgeTo (lS, lB, rgtN + 1, direct, true); + DoEdgeTo (lS, lB, rgtN, direct, true); + } + else + { + DoEdgeTo (lS, lB, rgtN, direct, true); + } + } + else + { + DoEdgeTo (lS, lB, p, direct, true); + } + lp = p; + } + } + } + else // edge is bottom to top + { + if (lS->eData[lB].rdx[0] >= 0) // edge is bottom to top and left to right + { + + for (int p = rgtN; p >= lftN; p--) + { + if (avoidDiag && p == rgtN && getPoint(rgtN).x[0] == getPoint(lp).x[0] - dd) + { + if (rgtN < numberOfPoints() && rgtN + 1 < lastPointNo + && getPoint(rgtN + 1).x[0] == getPoint(lp).x[0]) + { + DoEdgeTo (lS, lB, rgtN + 1, direct, false); // draw an edge that goes down + DoEdgeTo (lS, lB, rgtN, direct, false); // draw an edge that goes left + } + else + { + DoEdgeTo (lS, lB, rgtN, direct, false); + } + } + else + { + DoEdgeTo (lS, lB, p, direct, false); + } + lp = p; + } + } + else + { + // bottom to top and right to left edge + for (int p = lftN; p <= rgtN; p++) + { + // totally identical as the first block that I explain with a figure in the header docs + if (avoidDiag && p == lftN && getPoint(lftN).x[0] == getPoint(lp).x[0] + dd) + { + if (lftN > 0 && lftN - 1 >= lastChgtPt + && getPoint(lftN - 1).x[0] == getPoint(lp).x[0]) + { + DoEdgeTo (lS, lB, lftN - 1, direct, false); + DoEdgeTo (lS, lB, lftN, direct, false); + } + else + { + DoEdgeTo (lS, lB, lftN, direct, false); + } + } + else + { + DoEdgeTo (lS, lB, p, direct, false); + } + lp = p; + } + } + } + lS->swsData[lB].curPoint = lp; + } + // see how doneTo is being set to lastPointNo - 1? See the figure in the header docs of this function and you'll see + // what lastPointNo is, subtract one and you get the right most point that's just above lastPointNo. This marks that + // this edge has been processed til that y level. + lS->swsData[lB].doneTo = lastPointNo - 1; +} + +void +Shape::DoEdgeTo (Shape * iS, int iB, int iTo, bool direct, bool sens) +{ + int lp = iS->swsData[iB].curPoint; + int ne = -1; + if (sens) + { + if (direct) + ne = AddEdge (lp, iTo); + else + ne = AddEdge (iTo, lp); + } + else + { + if (direct) + ne = AddEdge (iTo, lp); + else + ne = AddEdge (lp, iTo); + } + if (ne >= 0 && _has_back_data) + { + ebData[ne].pathID = iS->ebData[iB].pathID; + ebData[ne].pieceID = iS->ebData[iB].pieceID; + if (iS->eData[iB].length < 0.00001) + { + ebData[ne].tSt = ebData[ne].tEn = iS->ebData[iB].tSt; + } + else + { + double bdl = iS->eData[iB].ilength; + Geom::Point bpx = iS->pData[iS->getEdge(iB).st].rx; + Geom::Point bdx = iS->eData[iB].rdx; + Geom::Point psx = getPoint(getEdge(ne).st).x; + Geom::Point pex = getPoint(getEdge(ne).en).x; + Geom::Point psbx=psx-bpx; + Geom::Point pebx=pex-bpx; + double pst = dot(psbx,bdx) * bdl; + double pet = dot(pebx,bdx) * bdl; + pst = iS->ebData[iB].tSt * (1 - pst) + iS->ebData[iB].tEn * pst; + pet = iS->ebData[iB].tSt * (1 - pet) + iS->ebData[iB].tEn * pet; + ebData[ne].tEn = pet; + ebData[ne].tSt = pst; + } + } + iS->swsData[iB].curPoint = iTo; + if (ne >= 0) + { + int cp = iS->swsData[iB].firstLinkedPoint; + swsData[ne].firstLinkedPoint = iS->swsData[iB].firstLinkedPoint; + while (cp >= 0) + { + pData[cp].askForWindingB = ne; + cp = pData[cp].nextLinkedPoint; + } + iS->swsData[iB].firstLinkedPoint = -1; + } +} diff --git a/src/livarot/float-line.cpp b/src/livarot/float-line.cpp new file mode 100644 index 0000000..9a19729 --- /dev/null +++ b/src/livarot/float-line.cpp @@ -0,0 +1,916 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Implementation of coverage with floating-point values. + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifdef faster_flatten +# include <cmath> // std::abs(float) +#endif +#include <cstdio> +#include "livarot/float-line.h" +#include "livarot/int-line.h" +#include <cstdio> + +FloatLigne::FloatLigne() +{ + s_first = s_last = -1; +} + + +FloatLigne::~FloatLigne() += default; + +/// Reset the line to empty (boundaries and runs). +void FloatLigne::Reset() +{ + bords.clear(); + runs.clear(); + s_first = s_last = -1; +} + +/** + * Add a coverage portion. + * + * \param guess Position from where we should try to insert the first + * boundary, or -1 if we don't have a clue. + */ +int FloatLigne::AddBord(float spos, float sval, float epos, float eval, int guess) +{ +// if ( showCopy ) printf("b= %f %f -> %f %f \n",spos,sval,epos,eval); + if ( spos >= epos ) { + return -1; + } + + float pente = (eval - sval) / (epos - spos); + +#ifdef faster_flatten + if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) { + return -1; + epos = spos; + pente = 0; + } +#endif + + if ( guess >= int(bords.size()) ) { + guess = -1; + } + + // add the left boundary + float_ligne_bord b; + int n = bords.size(); + b.pos = spos; + b.val = sval; + b.start = true; + b.other = n + 1; + b.pente = pente; + b.s_prev = b.s_next = -1; + bords.push_back(b); + + // insert it in the doubly-linked list + InsertBord(n, spos, guess); + + // add the right boundary + n = bords.size(); + b.pos = epos; + b.val = eval; + b.start = false; + b.other = n-1; + b.pente = pente; + b.s_prev = b.s_next = -1; + bords.push_back(b); + + // insert it in the doubly-linked list, knowing that boundary at index n-1 is not too far before me + InsertBord(n, epos, n - 1); + + return n; +} + +/** + * Add a coverage portion. + * + * \param guess Position from where we should try to insert the first + * boundary, or -1 if we don't have a clue. + */ +int FloatLigne::AddBord(float spos, float sval, float epos, float eval, float pente, int guess) +{ +// if ( showCopy ) printf("b= %f %f -> %f %f \n",spos,sval,epos,eval); + if ( spos >= epos ) { + return -1; + } + +#ifdef faster_flatten + if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) { + return -1; + epos = spos; + pente = 0; + } +#endif + + if ( guess >= int(bords.size()) ) { + guess=-1; + } + + float_ligne_bord b; + int n = bords.size(); + b.pos = spos; + b.val = sval; + b.start = true; + b.other = n + 1; + b.pente = pente; + b.s_prev = b.s_next = -1; + bords.push_back(b); + + n = bords.size(); + b.pos = epos; + b.val = eval; + b.start = false; + b.other = n - 1; + b.pente = pente; + b.s_prev = b.s_next = -1; + bords.push_back(b); + + InsertBord(n - 1, spos, guess); + InsertBord(n, epos, n - 1); +/* if ( bords[n-1].s_next < 0 ) { + bords[n].s_next=-1; + s_last=n; + + bords[n].s_prev=n-1; + bords[n-1].s_next=n; + } else if ( bords[bords[n-1].s_next].pos >= epos ) { + bords[n].s_next=bords[n-1].s_next; + bords[bords[n].s_next].s_prev=n; + + bords[n].s_prev=n-1; + bords[n-1].s_next=n; + } else { + int c=bords[bords[n-1].s_next].s_next; + while ( c >= 0 && bords[c].pos < epos ) c=bords[c].s_next; + if ( c < 0 ) { + bords[n].s_prev=s_last; + bords[s_last].s_next=n; + s_last=n; + } else { + bords[n].s_prev=bords[c].s_prev; + bords[bords[n].s_prev].s_next=n; + + bords[n].s_next=c; + bords[c].s_prev=n; + } + + }*/ + return n; +} + +/** + * Add a coverage portion. + * + * \param guess Position from where we should try to insert the last + * boundary, or -1 if we don't have a clue. + */ +int FloatLigne::AddBordR(float spos, float sval, float epos, float eval, float pente, int guess) +{ +// if ( showCopy ) printf("br= %f %f -> %f %f \n",spos,sval,epos,eval); +// return AddBord(spos,sval,epos,eval,pente,guess); + if ( spos >= epos ){ + return -1; + } + +#ifdef faster_flatten + if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) { + return -1; + epos = spos; + pente = 0; + } +#endif + + if ( guess >= int(bords.size()) ) { + guess=-1; + } + + float_ligne_bord b; + int n = bords.size(); + b.pos = spos; + b.val = sval; + b.start = true; + b.other = n + 1; + b.pente = pente; + b.s_prev = b.s_next = -1; + bords.push_back(b); + + n = bords.size(); + b.pos = epos; + b.val = eval; + b.start = false; + b.other = n - 1; + b.pente = pente; + b.s_prev = b.s_next = -1; + bords.push_back(b); + + InsertBord(n, epos, guess); + InsertBord(n - 1, spos, n); + +/* if ( bords[n].s_prev < 0 ) { + bords[n-1].s_prev=-1; + s_first=n-1; + + bords[n-1].s_next=n; + bords[n].s_prev=n-1; + } else if ( bords[bords[n].s_prev].pos <= spos ) { + bords[n-1].s_prev=bords[n].s_prev; + bords[bords[n-1].s_prev].s_next=n-1; + + bords[n-1].s_next=n; + bords[n].s_prev=n-1; + } else { + int c=bords[bords[n].s_prev].s_prev; + while ( c >= 0 && bords[c].pos > spos ) c=bords[c].s_prev; + if ( c < 0 ) { + bords[n-1].s_next=s_first; + bords[s_first].s_prev=n-1; + s_first=n-1; + } else { + bords[n-1].s_next=bords[c].s_next; + bords[bords[n-1].s_next].s_prev=n-1; + + bords[n-1].s_prev=c; + bords[c].s_next=n-1; + } + + }*/ + return n - 1; +} + +/** + * Add a coverage portion by appending boundaries at the end of the list. + * + * This works because we know they are on the right. + */ +int FloatLigne::AppendBord(float spos, float sval, float epos, float eval, float pente) +{ +// if ( showCopy ) printf("b= %f %f -> %f %f \n",spos,sval,epos,eval); +// return AddBord(spos,sval,epos,eval,pente,s_last); + if ( spos >= epos ) { + return -1; + } + +#ifdef faster_flatten + if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) { + return -1; + epos = spos; + pente = 0; + } +#endif + + int n = bords.size(); + float_ligne_bord b; + b.pos = spos; + b.val = sval; + b.start = true; + b.other = n + 1; + b.pente = pente; + b.s_prev = s_last; + b.s_next = n + 1; + bords.push_back(b); + + if ( s_last >= 0 ) { + bords[s_last].s_next = n; + } + + if ( s_first < 0 ) { + s_first = n; + } + + n = bords.size(); + b.pos = epos; + b.val = eval; + b.start = false; + b.other = n - 1; + b.pente = pente; + b.s_prev = n - 1; + b.s_next = -1; + bords.push_back(b); + + s_last = n; + + return n; +} + + + +// insertion in a boubly-linked list. nothing interesting here +void FloatLigne::InsertBord(int no, float /*p*/, int guess) +{ +// TODO check if ignoring p is bad + if ( no < 0 || no >= int(bords.size()) ) { + return; + } + + if ( s_first < 0 ) { + s_first = s_last = no; + bords[no].s_prev = -1; + bords[no].s_next = -1; + return; + } + + if ( guess < 0 || guess >= int(bords.size()) ) { + int c = s_first; + while ( c >= 0 && c < int(bords.size()) && CmpBord(bords[c], bords[no]) < 0 ) { + c = bords[c].s_next; + } + + if ( c < 0 || c >= int(bords.size()) ) { + bords[no].s_prev = s_last; + bords[s_last].s_next = no; + s_last = no; + } else { + bords[no].s_prev = bords[c].s_prev; + if ( bords[no].s_prev >= 0 ) { + bords[bords[no].s_prev].s_next = no; + } else { + s_first = no; + } + bords[no].s_next = c; + bords[c].s_prev = no; + } + } else { + int c = guess; + int stTst = CmpBord(bords[c], bords[no]); + + if ( stTst == 0 ) { + + bords[no].s_prev = bords[c].s_prev; + if ( bords[no].s_prev >= 0 ) { + bords[bords[no].s_prev].s_next = no; + } else { + s_first = no; + } + bords[no].s_next = c; + bords[c].s_prev = no; + + } else if ( stTst > 0 ) { + + while ( c >= 0 && c < int(bords.size()) && CmpBord(bords[c], bords[no]) > 0 ) { + c = bords[c].s_prev; + } + + if ( c < 0 || c >= int(bords.size()) ) { + bords[no].s_next = s_first; + bords[s_first].s_prev =no; // s_first != -1 + s_first = no; + } else { + bords[no].s_next = bords[c].s_next; + if ( bords[no].s_next >= 0 ) { + bords[bords[no].s_next].s_prev = no; + } else { + s_last = no; + } + bords[no].s_prev = c; + bords[c].s_next = no; + } + + } else { + + while ( c >= 0 && c < int(bords.size()) && CmpBord(bords[c],bords[no]) < 0 ) { + c = bords[c].s_next; + } + + if ( c < 0 || c >= int(bords.size()) ) { + bords[no].s_prev = s_last; + bords[s_last].s_next = no; + s_last = no; + } else { + bords[no].s_prev = bords[c].s_prev; + if ( bords[no].s_prev >= 0 ) { + bords[bords[no].s_prev].s_next = no; + } else { + s_first = no; + } + bords[no].s_next = c; + bords[c].s_prev = no; + } + } + } +} + +/** + * Computes the sum of the coverages of the runs currently being scanned, + * of which there are "pending". + */ +float FloatLigne::RemainingValAt(float at, int pending) +{ + float sum = 0; +/* int no=firstAc; + while ( no >= 0 && no < bords.size() ) { + int nn=bords[no].other; + sum+=bords[nn].val+(at-bords[nn].pos)*bords[nn].pente; +// sum+=((at-bords[nn].pos)*bords[no].val+(bords[no].pos-at)*bords[nn].val)/(bords[no].pos-bords[nn].pos); +// sum+=ValAt(at,bords[nn].pos,bords[no].pos,bords[nn].val,bords[no].val); + no=bords[no].next; + }*/ + // for each portion being scanned, compute coverage at position "at" and sum. + // we could simply compute the sum of portion coverages as a "f(x)=ux+y" and evaluate it at "x=at", + // but there are numerical problems with this approach, and it produces ugly lines of incorrectly + // computed alpha values, so i reverted to this "safe but slow" version + + for (int i=0; i < pending; i++) { + int const nn = bords[i].pend_ind; + sum += bords[nn].val + (at - bords[nn].pos) * bords[nn].pente; + } + + return sum; +} + + +/** + * Extract a set of non-overlapping runs from the boundaries. + * + * We scan the boundaries left to right, maintaining a set of coverage + * portions currently being scanned. For each such portion, the function + * will add the index of its first boundary in an array; but instead of + * allocating another array, it uses a field in float_ligne_bord: pend_ind. + * The outcome is that an array of float_ligne_run is produced. + */ +void FloatLigne::Flatten() +{ + if ( int(bords.size()) <= 1 ) { + Reset(); + return; + } + + runs.clear(); + +// qsort(bords,bords.size(),sizeof(float_ligne_bord),FloatLigne::CmpBord); +// SortBords(0,bords.size()-1); + + float totPente = 0; + float totStart = 0; + float totX = bords[0].pos; + + bool startExists = false; + float lastStart = 0; + float lastVal = 0; + int pending = 0; + +// for (int i=0;i<bords.size();) { + // read the list from left to right, adding a run for each boundary crossed, minus runs with alpha=0 + for (int i=/*0*/s_first; i>=0 && i < int(bords.size()) ;) { + + float cur = bords[i].pos; // position of the current boundary (there may be several boundaries at this position) + float leftV = 0; // deltas in coverage value at this position + float rightV = 0; + float leftP = 0; // deltas in coverage increase per unit length at this position + float rightP = 0; + + // more precisely, leftV is the sum of decreases of coverage value, + // while rightV is the sum of increases, so that leftV+rightV is the delta. + // idem for leftP and rightP + + // start by scanning all boundaries that end a portion at this position + while ( i >= 0 && i < int(bords.size()) && bords[i].pos == cur && bords[i].start == false ) { + leftV += bords[i].val; + leftP += bords[i].pente; + +#ifndef faster_flatten + // we need to remove the boundary that started this coverage portion for the pending list + if ( bords[i].other >= 0 && bords[i].other < int(bords.size()) ) { + // so we use the pend_inv "array" + int const k = bords[bords[i].other].pend_inv; + if ( k >= 0 && k < pending ) { + // and update the pend_ind array and its inverse pend_inv + bords[k].pend_ind = bords[pending - 1].pend_ind; + bords[bords[k].pend_ind].pend_inv = k; + } + } +#endif + + // one less portion pending + pending--; + // and we move to the next boundary in the doubly linked list + i=bords[i].s_next; + //i++; + } + + // then scan all boundaries that start a portion at this position + while ( i >= 0 && i < int(bords.size()) && bords[i].pos == cur && bords[i].start ) { + rightV += bords[i].val; + rightP += bords[i].pente; +#ifndef faster_flatten + bords[pending].pend_ind=i; + bords[i].pend_inv=pending; +#endif + pending++; + i = bords[i].s_next; + //i++; + } + + // coverage value at end of the run will be "start coverage"+"delta per unit length"*"length" + totStart = totStart + totPente * (cur - totX); + + if ( startExists ) { + // add that run + AddRun(lastStart, cur, lastVal, totStart, totPente); + } + // update "delta coverage per unit length" + totPente += rightP - leftP; + // not really needed here + totStart += rightV - leftV; + // update position + totX = cur; + if ( pending > 0 ) { + startExists = true; + +#ifndef faster_flatten + // to avoid accumulation of numerical errors, we compute an accurate coverage for this position "cur" + totStart = RemainingValAt(cur, pending); +#endif + lastVal = totStart; + lastStart = cur; + } else { + startExists = false; + totStart = 0; + totPente = 0; + } + } +} + + +/// Debug dump of the instance. +void FloatLigne::Affiche() +{ + printf("%lu : \n", (long unsigned int) bords.size()); + for (auto & bord : bords) { + printf("(%f %f %f %i) ",bord.pos,bord.val,bord.pente,(bord.start?1:0)); // localization ok + } + + printf("\n"); + printf("%lu : \n", (long unsigned int) runs.size()); + + for (auto & run : runs) { + printf("(%f %f -> %f %f / %f)", + run.st, run.vst, run.en, run.ven, run.pente); // localization ok + } + + printf("\n"); +} + + +int FloatLigne::AddRun(float st, float en, float vst, float ven) +{ + return AddRun(st, en, vst, ven, (ven - vst) / (en - st)); +} + + +int FloatLigne::AddRun(float st, float en, float vst, float ven, float pente) +{ + if ( st >= en ) { + return -1; + } + + int const n = runs.size(); + float_ligne_run r; + r.st = st; + r.en = en; + r.vst = vst; + r.ven = ven; + r.pente = pente; + runs.push_back(r); + + return n; +} + +void FloatLigne::Copy(FloatLigne *a) +{ + if ( a->runs.empty() ) { + Reset(); + return; + } + + bords.clear(); + runs = a->runs; +} + +void FloatLigne::Copy(IntLigne *a) +{ + if ( a->nbRun ) { + Reset(); + return; + } + + bords.clear(); + runs.resize(a->nbRun); + + for (int i = 0; i < int(runs.size()); i++) { + runs[i].st = a->runs[i].st; + runs[i].en = a->runs[i].en; + runs[i].vst = a->runs[i].vst; + runs[i].ven = a->runs[i].ven; + } +} + +/// Cuts the parts having less than tresh coverage. +void FloatLigne::Min(FloatLigne *a, float tresh, bool addIt) +{ + Reset(); + if ( a->runs.empty() ) { + return; + } + + bool startExists = false; + float lastStart=0; + float lastEnd = 0; + + for (auto runA : a->runs) { + if ( runA.vst <= tresh ) { + if ( runA.ven <= tresh ) { + if ( startExists ) { + if ( lastEnd >= runA.st - 0.00001 ) { + lastEnd = runA.en; + } else { + if ( addIt ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } + lastStart = runA.st; + lastEnd = runA.en; + } + } else { + lastStart = runA.st; + lastEnd = runA.en; + } + startExists = true; + } else { + float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven); + if ( startExists ) { + if ( lastEnd >= runA.st - 0.00001 ) { + if ( addIt ) { + AddRun(lastStart, cutPos, tresh, tresh); + } + AddRun(cutPos,runA.en, tresh, runA.ven); + } else { + if ( addIt ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } + if ( addIt ) { + AddRun(runA.st, cutPos, tresh, tresh); + } + AddRun(cutPos, runA.en, tresh, runA.ven); + } + } else { + if ( addIt ) { + AddRun(runA.st, cutPos, tresh, tresh); + } + AddRun(cutPos, runA.en, tresh, runA.ven); + } + startExists = false; + } + + } else { + + if ( runA.ven <= tresh ) { + float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh - runA.vst)) / (runA.ven - runA.vst); + if ( startExists ) { + if ( addIt ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } + } + AddRun(runA.st, cutPos, runA.vst, tresh); + startExists = true; + lastStart = cutPos; + lastEnd = runA.en; + } else { + if ( startExists ) { + if ( addIt ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } + } + startExists = false; + AddRun(runA.st, runA.en, runA.vst, runA.ven); + } + } + } + + if ( startExists ) { + if ( addIt ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } + } +} + +/** + * Cuts the coverage a in 2 parts. + * + * over will receive the parts where coverage > tresh, while the present + * FloatLigne will receive the parts where coverage <= tresh. + */ +void FloatLigne::Split(FloatLigne *a, float tresh, FloatLigne *over) +{ + Reset(); + if ( a->runs.empty() ) { + return; + } + + for (auto runA : a->runs) { + if ( runA.vst >= tresh ) { + if ( runA.ven >= tresh ) { + if ( over ) { + over->AddRun(runA.st, runA.en, runA.vst, runA.ven); + } + } else { + float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven); + if ( over ) { + over->AddRun(runA.st, cutPos, runA.vst, tresh); + } + AddRun(cutPos, runA.en, tresh, runA.ven); + } + } else { + if ( runA.ven >= tresh ) { + float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh-runA.vst)) / (runA.ven - runA.vst); + AddRun(runA.st, cutPos, runA.vst, tresh); + if ( over ) { + over->AddRun(cutPos, runA.en, tresh, runA.ven); + } + } else { + AddRun(runA.st, runA.en, runA.vst, runA.ven); + } + } + } +} + +/** + * Clips the coverage runs to tresh. + * + * If addIt is false, it only leaves the parts that are not entirely under + * tresh. If addIt is true, it's the coverage clamped to tresh. + */ +void FloatLigne::Max(FloatLigne *a, float tresh, bool addIt) +{ + Reset(); + if ( a->runs.empty() <= 0 ) { + return; + } + + bool startExists = false; + float lastStart = 0; + float lastEnd = 0; + for (auto runA : a->runs) { + if ( runA.vst >= tresh ) { + if ( runA.ven >= tresh ) { + if ( startExists ) { + if ( lastEnd >= runA.st-0.00001 ) { + lastEnd = runA.en; + } else { + if ( addIt ) { + AddRun(lastStart,lastEnd,tresh,tresh); + } + lastStart = runA.st; + lastEnd = runA.en; + } + } else { + lastStart = runA.st; + lastEnd = runA.en; + } + startExists = true; + } else { + float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven); + if ( startExists ) { + if ( lastEnd >= runA.st-0.00001 ) { + if ( addIt ) { + AddRun(lastStart, cutPos, tresh, tresh); + } + AddRun(cutPos, runA.en, tresh, runA.ven); + } else { + if ( addIt ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } + if ( addIt ) { + AddRun(runA.st, cutPos, tresh, tresh); + } + AddRun(cutPos, runA.en, tresh, runA.ven); + } + } else { + if ( addIt ) { + AddRun(runA.st, cutPos, tresh, tresh); + } + AddRun(cutPos, runA.en, tresh, runA.ven); + } + startExists = false; + } + + } else { + + if ( runA.ven >= tresh ) { + float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh - runA.vst)) / (runA.ven - runA.vst); + if ( startExists ) { + if ( addIt ) { + AddRun(lastStart,lastEnd,tresh,tresh); + } + } + AddRun(runA.st, cutPos, runA.vst, tresh); + startExists = true; + lastStart = cutPos; + lastEnd = runA.en; + } else { + if ( startExists ) { + if ( addIt ) { + AddRun(lastStart,lastEnd,tresh,tresh); + } + } + startExists = false; + AddRun(runA.st, runA.en, runA.vst, runA.ven); + } + } + } + + if ( startExists ) { + if ( addIt ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } + } +} + +/// Extract the parts where coverage > tresh. +void FloatLigne::Over(FloatLigne *a, float tresh) +{ + Reset(); + if ( a->runs.empty() ) { + return; + } + + bool startExists = false; + float lastStart = 0; + float lastEnd = 0; + + for (auto runA : a->runs) { + if ( runA.vst >= tresh ) { + if ( runA.ven >= tresh ) { + if ( startExists ) { + if ( lastEnd >= runA.st - 0.00001 ) { + lastEnd = runA.en; + } else { + AddRun(lastStart, lastEnd, tresh, tresh); + lastStart = runA.st; + lastEnd = runA.en; + } + } else { + lastStart = runA.st; + lastEnd = runA.en; + } + startExists = true; + + } else { + + float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven); + if ( startExists ) { + if ( lastEnd >= runA.st - 0.00001 ) { + AddRun(lastStart, cutPos, tresh, tresh); + } else { + AddRun(lastStart, lastEnd, tresh, tresh); + AddRun(runA.st, cutPos, tresh, tresh); + } + } else { + AddRun(runA.st, cutPos, tresh, tresh); + } + startExists = false; + } + + } else { + if ( runA.ven >= tresh ) { + float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh - runA.vst)) / (runA.ven - runA.vst); + if ( startExists ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } + startExists = true; + lastStart = cutPos; + lastEnd = runA.en; + } else { + if ( startExists ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } + startExists = false; + } + } + } + + if ( startExists ) { + AddRun(lastStart, lastEnd, tresh, tresh); + } +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/float-line.h b/src/livarot/float-line.h new file mode 100644 index 0000000..ebbd9aa --- /dev/null +++ b/src/livarot/float-line.h @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2010 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef INKSCAPE_LIVAROT_FLOAT_LINE_H +#define INKSCAPE_LIVAROT_FLOAT_LINE_H + +/** \file + * Coverage with floating-point boundaries. + */ + +#include <vector> +#include "livarot/LivarotDefs.h" + +class IntLigne; + +/// A coverage portion ("run") with floating point boundaries. +struct float_ligne_run { + float st; + float en; + float vst; + float ven; + float pente; ///< (ven-vst)/(en-st) +}; + +/** + * A floating-point boundary. + * + * Each float_ligne_bord is a boundary of some coverage. + * The Flatten() function will extract non-overlapping runs and produce an + * array of float_ligne_run. The float_ligne_bord are stored in an array, but + * linked like a doubly-linked list. + * + * The idea behind that is that a given edge produces one float_ligne_bord at + * the beginning of Scan() and possibly another in AvanceEdge() and + * DestroyEdge(); but that second float_ligne_bord will not be far away in + * the list from the first, so it's faster to salvage the index of the first + * float_ligne_bord and try to insert the second from that salvaged position. + */ +struct float_ligne_bord { + float pos; ///< position of the boundary + bool start; ///< is the beginning of the coverage portion? + float val; ///< amount of coverage (ie vst if start==true, and ven if start==false) + float pente; ///< (ven-vst)/(en-st) + int other; ///< index, in the array of float_ligne_bord, of the other boundary associated to this one + int s_prev; ///< index of the previous bord in the doubly-linked list + int s_next; ///< index of the next bord in the doubly-linked list + int pend_ind; ///< bords[i].pend_ind is the index of the float_ligne_bord that is the start of the + ///< coverage portion being scanned (in the Flatten() ) + int pend_inv; ///< inverse of pend_ind, for faster handling of insertion/removal in the "pending" array +}; + +/** + * Coverage with floating-point boundaries. + * + * The goal is to salvage exact coverage info in the sweepline performed by + * Scan() or QuickScan(), then clean up a bit, convert floating point bounds + * to integer bounds, because pixel have integer bounds, and then raster runs + * of the type: + * \verbatim + position on the (pixel) line: st en + | | + coverage value (0=empty, 1=full) vst -> ven \endverbatim + */ +class FloatLigne { +public: + std::vector<float_ligne_bord> bords; ///< vector of coverage boundaries + std::vector<float_ligne_run> runs; ///< vector of runs + + /// first boundary in the doubly-linked list + int s_first; + /// last boundary in the doubly-linked list + int s_last; + + FloatLigne(); + virtual ~FloatLigne(); + + void Reset(); + + int AddBord(float spos, float sval, float epos, float eval, int guess = -1); + int AddBord(float spos, float sval, float epos, float eval, float pente, int guess = -1); + int AddBordR(float spos, float sval, float epos, float eval, float pente, int guess = -1); + int AppendBord(float spos, float sval, float epos, float eval, float pente); + + void Flatten(); + + void Affiche(); + + void Max(FloatLigne *a, float tresh, bool addIt); + + void Min(FloatLigne *a, float tresh, bool addIt); + + void Split(FloatLigne *a, float tresh, FloatLigne *over); + + void Over(FloatLigne *a, float tresh); + + void Copy(IntLigne *a); + void Copy(FloatLigne *a); + + float RemainingValAt(float at, int pending); + + static int CmpBord(float_ligne_bord const &d1, float_ligne_bord const &d2) { + if ( d1.pos == d2.pos ) { + if ( d1.start && !(d2.start) ) { + return 1; + } + if ( !(d1.start) && d2.start ) { + return -1; + } + return 0; + } + + return (( d1.pos < d2.pos ) ? -1 : 1); + }; + + int AddRun(float st, float en, float vst, float ven, float pente); + +private: + void InsertBord(int no, float p, int guess); + int AddRun(float st, float en, float vst, float ven); + + inline float ValAt(float at, float ps, float pe, float vs, float ve) { + return ((at - ps) * ve + (pe - at) * vs) / (pe - ps); + }; +}; + +#endif + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/int-line.cpp b/src/livarot/int-line.cpp new file mode 100644 index 0000000..ff87475 --- /dev/null +++ b/src/livarot/int-line.cpp @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Implementation of coverage with integer boundaries. + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glib.h> +#include <cmath> +#include <cstring> +#include <string> +#include <cstdlib> +#include <cstdio> +#include "livarot/int-line.h" +#include "livarot/float-line.h" +#include "livarot/BitLigne.h" + +IntLigne::IntLigne() +{ + nbBord = maxBord = 0; + bords = nullptr; + + nbRun = maxRun = 0; + runs = nullptr; + + firstAc = lastAc = -1; +} + + +IntLigne::~IntLigne() +{ + if ( maxBord > 0 ) { + g_free(bords); + nbBord = maxBord = 0; + bords = nullptr; + } + if ( maxRun > 0 ) { + g_free(runs); + nbRun = maxRun = 0; + runs = nullptr; + } +} + +void IntLigne::Reset() +{ + nbBord = 0; + nbRun = 0; + firstAc = lastAc = -1; +} + +int IntLigne::AddBord(int spos, float sval, int epos, float eval) +{ + if ( nbBord + 1 >= maxBord ) { + maxBord = 2 * nbBord + 2; + bords = (int_ligne_bord *) g_realloc(bords, maxBord * sizeof(int_ligne_bord)); + + } + + int n = nbBord++; + bords[n].pos = spos; + bords[n].val = sval; + bords[n].start = true; + bords[n].other = n+1; + bords[n].prev = bords[n].next = -1; + + n = nbBord++; + bords[n].pos = epos; + bords[n].val = eval; + bords[n].start = false; + bords[n].other = n-1; + bords[n].prev = bords[n].next = -1; + + return n - 1; +} + + +float IntLigne::RemainingValAt(int at) +{ + int no = firstAc; + float sum = 0; + while ( no >= 0 ) { + int nn = bords[no].other; + sum += ValAt(at, bords[nn].pos, bords[no].pos, bords[nn].val, bords[no].val); + no = bords[no].next; + } + return sum; +} + +void IntLigne::Flatten() +{ + if ( nbBord <= 1 ) { + Reset(); + return; + } + + nbRun = 0; + firstAc = lastAc = -1; + + for (int i = 0; i < nbBord; i++) { + bords[i].prev = i; + } + + qsort(bords, nbBord, sizeof(int_ligne_bord), IntLigne::CmpBord); + for (int i = 0; i < nbBord; i++) { + bords[bords[i].prev].next = i; + } + + for (int i = 0; i < nbBord; i++) { + bords[i].other = bords[bords[i].other].next; + } + + int lastStart = 0; + float lastVal = 0; + bool startExists = false; + + for (int i = 0; i < nbBord; ) { + int cur = bords[i].pos; + float leftV = 0; + float rightV = 0; + float midV = 0; + while ( i < nbBord && bords[i].pos == cur && bords[i].start == false ) { + Dequeue(i); + leftV += bords[i].val; + i++; + } + midV = RemainingValAt(cur); + while ( i < nbBord && bords[i].pos == cur && bords[i].start ) { + rightV += bords[i].val; + Enqueue(bords[i].other); + i++; + } + + if ( startExists ) { + AddRun(lastStart, cur, lastVal, leftV + midV); + } + if ( firstAc >= 0 ) { + startExists = true; + lastVal = midV + rightV; + lastStart = cur; + } else { + startExists = false; + } + } +} + + +void IntLigne::Affiche() +{ + printf("%i : \n", nbRun); + for (int i = 0; i < nbRun;i++) { + printf("(%i %f -> %i %f) ", runs[i].st, runs[i].vst, runs[i].en, runs[i].ven); // localization ok + } + printf("\n"); +} + +int IntLigne::AddRun(int st, int en, float vst, float ven) +{ + if ( st >= en ) { + return -1; + } + + if ( nbRun >= maxRun ) { + maxRun = 2 * nbRun + 1; + runs = (int_ligne_run *) g_realloc(runs, maxRun * sizeof(int_ligne_run)); + } + + int n = nbRun++; + runs[n].st = st; + runs[n].en = en; + runs[n].vst = vst; + runs[n].ven = ven; + return n; +} + +void IntLigne::Booleen(IntLigne *a, IntLigne *b, BooleanOp mod) +{ + Reset(); + if ( a->nbRun <= 0 && b->nbRun <= 0 ) { + return; + } + + if ( a->nbRun <= 0 ) { + if ( mod == bool_op_union || mod == bool_op_symdiff ) { + Copy(b); + } + return; + } + + if ( b->nbRun <= 0 ) { + if ( mod == bool_op_union || mod == bool_op_diff || mod == bool_op_symdiff ) { + Copy(a); + } + return; + } + + int curA = 0; + int curB = 0; + int curPos = (a->runs[0].st < b->runs[0].st) ? a->runs[0].st : b->runs[0].st; + int nextPos = curPos; + float valA = 0; + float valB = 0; + if ( curPos == a->runs[0].st ) { + valA = a->runs[0].vst; + } + if ( curPos == b->runs[0].st ) { + valB = b->runs[0].vst; + } + + while ( curA < a->nbRun && curB < b->nbRun ) { + int_ligne_run runA = a->runs[curA]; + int_ligne_run runB = b->runs[curB]; + const bool inA = ( curPos >= runA.st && curPos < runA.en ); + const bool inB = ( curPos >= runB.st && curPos < runB.en ); + + bool startA = false; + bool startB = false; + bool endA = false; + bool endB = false; + + if ( curPos < runA.st ) { + if ( curPos < runB.st ) { + startA = runA.st <= runB.st; + startB = runA.st >= runB.st; + nextPos = startA ? runA.st : runB.st; + } else if ( curPos >= runB.st ) { + startA = runA.st <= runB.en; + endB = runA.st >= runB.en; + nextPos = startA ? runA.st : runB.en; + } + } else if ( curPos == runA.st ) { + if ( curPos < runB.st ) { + endA = runA.en <= runB.st; + startB = runA.en >= runB.st; + nextPos = startB ? runB.en : runA.st; + } else if ( curPos == runB.st ) { + endA = runA.en <= runB.en; + endB = runA.en >= runB.en; + nextPos = endA? runA.en : runB.en; + } else { + endA = runA.en <= runB.en; + endB = runA.en >= runB.en; + nextPos = endA ? runA.en : runB.en; + } + } else { + if ( curPos < runB.st ) { + endA = runA.en <= runB.st; + startB = runA.en >= runB.st; + nextPos = startB ? runB.st : runA.en; + } else if ( curPos == runB.st ) { + endA = runA.en <= runB.en; + endB = runA.en >= runB.en; + nextPos = endA ? runA.en : runB.en; + } else { + endA = runA.en <= runB.en; + endB = runA.en >= runB.en; + nextPos = endA ? runA.en : runB.en; + } + } + + float oValA = valA; + float oValB = valB; + valA = inA ? ValAt(nextPos, runA.st, runA.en, runA.vst, runA.ven) : 0; + valB = inB ? ValAt(nextPos, runB.st, runB.en, runB.vst, runB.ven) : 0; + + if ( mod == bool_op_union ) { + + if ( inA || inB ) { + AddRun(curPos, nextPos, oValA + oValB, valA + valB); + } + + } else if ( mod == bool_op_inters ) { + + if ( inA && inB ) { + AddRun(curPos, nextPos, oValA * oValB, valA * valB); + } + + } else if ( mod == bool_op_diff ) { + + if ( inA ) { + AddRun(curPos, nextPos, oValA - oValB, valA - valB); + } + + } else if ( mod == bool_op_symdiff ) { + if ( inA && !(inB) ) { + AddRun(curPos, nextPos, oValA - oValB, valA - valB); + } + if ( !(inA) && inB ) { + AddRun(curPos, nextPos, oValB - oValA, valB - valA); + } + } + + curPos = nextPos; + if ( startA ) { + // inA=true; these are never used + valA = runA.vst; + } + if ( startB ) { + //inB=true; + valB = runB.vst; + } + if ( endA ) { + //inA=false; + valA = 0; + curA++; + if ( curA < a->nbRun && a->runs[curA].st == curPos ) { + valA = a->runs[curA].vst; + } + } + if ( endB ) { + //inB=false; + valB = 0; + curB++; + if ( curB < b->nbRun && b->runs[curB].st == curPos ) { + valB = b->runs[curB].vst; + } + } + } + + while ( curA < a->nbRun ) { + int_ligne_run runA = a->runs[curA]; + const bool inA = ( curPos >= runA.st && curPos < runA.en ); + const bool inB = false; + + bool startA = false; + bool endA = false; + if ( curPos < runA.st ) { + nextPos = runA.st; + startA = true; + } else if ( curPos >= runA.st ) { + nextPos = runA.en; + endA = true; + } + + float oValA = valA; + float oValB = valB; + valA = inA ? ValAt(nextPos,runA.st, runA.en, runA.vst, runA.ven) : 0; + valB = 0; + + if ( mod == bool_op_union ) { + if ( inA || inB ) { + AddRun(curPos, nextPos, oValA + oValB, valA + valB); + } + } else if ( mod == bool_op_inters ) { + if ( inA && inB ) { + AddRun(curPos, nextPos, oValA * oValB, valA * valB); + } + } else if ( mod == bool_op_diff ) { + if ( inA ) { + AddRun(curPos, nextPos, oValA - oValB, valA - valB); + } + } else if ( mod == bool_op_symdiff ) { + if ( inA && !(inB) ) { + AddRun(curPos, nextPos, oValA - oValB, valA - valB); + } + if ( !(inA) && inB ) { + AddRun(curPos,nextPos,oValB-oValA,valB-valA); + } + } + + curPos = nextPos; + if ( startA ) { + //inA=true; + valA = runA.vst; + } + if ( endA ) { + //inA=false; + valA = 0; + curA++; + if ( curA < a->nbRun && a->runs[curA].st == curPos ) { + valA = a->runs[curA].vst; + } + } + } + + while ( curB < b->nbRun ) { + int_ligne_run runB = b->runs[curB]; + const bool inB = ( curPos >= runB.st && curPos < runB.en ); + const bool inA = false; + + bool startB = false; + bool endB = false; + if ( curPos < runB.st ) { + nextPos = runB.st; + startB = true; + } else if ( curPos >= runB.st ) { + nextPos = runB.en; + endB = true; + } + + float oValA = valA; + float oValB = valB; + valB = inB ? ValAt(nextPos, runB.st, runB.en, runB.vst, runB.ven) : 0; + valA = 0; + + if ( mod == bool_op_union ) { + if ( inA || inB ) { + AddRun(curPos, nextPos, oValA + oValB,valA + valB); + } + } else if ( mod == bool_op_inters ) { + if ( inA && inB ) { + AddRun(curPos, nextPos, oValA * oValB, valA * valB); + } + } else if ( mod == bool_op_diff ) { + if ( inA ) { + AddRun(curPos, nextPos, oValA - oValB, valA - valB); + } + } else if ( mod == bool_op_symdiff ) { + if ( inA && !(inB) ) { + AddRun(curPos, nextPos, oValA - oValB,valA - valB); + } + if ( !(inA) && inB ) { + AddRun(curPos, nextPos, oValB - oValA, valB - valA); + } + } + + curPos = nextPos; + if ( startB ) { + //inB=true; + valB = runB.vst; + } + if ( endB ) { + //inB=false; + valB = 0; + curB++; + if ( curB < b->nbRun && b->runs[curB].st == curPos ) { + valB = b->runs[curB].vst; + } + } + } +} + +/** + * Transform a line of bits into pixel coverage values. + * + * This is where you go from supersampled data to alpha values. + * \see IntLigne::Copy(int nbSub,BitLigne* *a). + */ +void IntLigne::Copy(BitLigne* a) +{ + if ( a->curMax <= a->curMin ) { + Reset(); + return; + } + + if ( a->curMin < a->st ) { + a->curMin = a->st; + } + + if ( a->curMax < a->st ) { + Reset(); + return; + } + + if ( a->curMin > a->en ) { + Reset(); + return; + } + + if ( a->curMax > a->en ) { + a->curMax=a->en; + } + + nbBord = 0; + nbRun = 0; + + int lastVal = 0; + int lastStart = 0; + bool startExists = false; + + int masks[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; + + uint32_t c_full = a->fullB[(a->curMin-a->st) >> 3]; + uint32_t c_part = a->partB[(a->curMin-a->st) >> 3]; + c_full <<= 4 * ((a->curMin - a->st) & 0x00000007); + c_part <<= 4 * ((a->curMin - a->st) & 0x00000007); + for (int i = a->curMin; i <= a->curMax; i++) { + int nbBit = masks[c_full >> 28] + masks[c_part >> 28]; + + if ( nbBit > 0 ) { + if ( startExists ) { + if ( lastVal == nbBit ) { + // on continue le run + } else { + AddRun(lastStart, i, ((float) lastVal) / 4, ((float) lastVal) / 4); + lastStart = i; + lastVal = nbBit; + } + } else { + lastStart = i; + lastVal = nbBit; + startExists = true; + } + } else { + if ( startExists ) { + AddRun(lastStart, i, ((float) lastVal) / 4, ((float) lastVal) / 4); + } + startExists = false; + } + int chg = (i + 1 - a->st) & 0x00000007; + if ( chg == 0 ) { + c_full = a->fullB[(i + 1 - a->st) >> 3]; + c_part = a->partB[(i + 1 - a->st) >> 3]; + } else { + c_full <<= 4; + c_part <<= 4; + } + } + if ( startExists ) { + AddRun(lastStart, a->curMax + 1, ((float) lastVal) / 4, ((float) lastVal) / 4); + } +} + +/** + * Transform a line of bits into pixel coverage values. + * + * Alpha values are computed from supersampled data, so we have to scan the + * BitLigne left to right, summing the bits in each pixel. The alpha value + * is then "number of bits"/(nbSub*nbSub)". Full bits and partial bits are + * treated as equals because the method produces ugly results otherwise. + * + * \param nbSub Number of BitLigne in the array "a". + */ +void IntLigne::Copy(int nbSub, BitLigne **as) +{ + if ( nbSub <= 0 ) { + Reset(); + return; + } + + if ( nbSub == 1 ) { + Copy(as[0]); + return; + } + + // compute the min-max of the pixels to be rasterized from the min-max of the inpur bitlignes + int curMin = as[0]->curMin; + int curMax = as[0]->curMax; + for (int i = 1; i < nbSub; i++) { + if ( as[i]->curMin < curMin ) { + curMin = as[i]->curMin; + } + if ( as[i]->curMax > curMax ) { + curMax = as[i]->curMax; + } + } + + if ( curMin < as[0]->st ) { + curMin = as[0]->st; + } + + if ( curMax > as[0]->en ) { + curMax = as[0]->en; + } + + if ( curMax <= curMin ) { + Reset(); + return; + } + + nbBord = 0; + nbRun = 0; + + int lastVal = 0; + int lastStart = 0; + bool startExists = false; + float spA; + int masks[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + + int theSt = as[0]->st; + if ( nbSub == 4 ) { + // special case for 4*4 supersampling, to avoid a few loops + uint32_t c_full[4]; + c_full[0] = as[0]->fullB[(curMin - theSt) >> 3] | as[0]->partB[(curMin - theSt) >> 3]; + c_full[0] <<= 4 * ((curMin - theSt) & 7); + c_full[1] = as[1]->fullB[(curMin - theSt) >> 3] | as[1]->partB[(curMin - theSt) >> 3]; + c_full[1] <<= 4 * ((curMin - theSt) & 7); + c_full[2] = as[2]->fullB[(curMin - theSt) >> 3] | as[2]->partB[(curMin - theSt) >> 3]; + c_full[2] <<= 4* ((curMin - theSt) & 7); + c_full[3] = as[3]->fullB[(curMin - theSt) >> 3] | as[3]->partB[(curMin - theSt) >> 3]; + c_full[3] <<= 4* ((curMin - theSt) & 7); + + spA = 1.0 / (4 * 4); + for (int i = curMin; i <= curMax; i++) { + int nbBit = 0; + + if ( c_full[0] == 0 && c_full[1] == 0 && c_full[2] == 0 && c_full[3] == 0 ) { + + if ( startExists ) { + AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA); + } + startExists = false; + i = theSt + (((i - theSt) & (~7) ) + 7); + + } else if ( c_full[0] == 0xFFFFFFFF && c_full[1] == 0xFFFFFFFF && + c_full[2] == 0xFFFFFFFF && c_full[3] == 0xFFFFFFFF ) { + + if ( startExists ) { + if ( lastVal == 4*4) { + } else { + AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA); + lastStart = i; + } + } else { + lastStart = i; + } + lastVal = 4*4; + startExists = true; + i = theSt + (((i - theSt) & (~7) ) + 7); + + } else { + nbBit += masks[c_full[0] >> 28]; + nbBit += masks[c_full[1] >> 28]; + nbBit += masks[c_full[2] >> 28]; + nbBit += masks[c_full[3] >> 28]; + + if ( nbBit > 0 ) { + if ( startExists ) { + if ( lastVal == nbBit ) { + // on continue le run + } else { + AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA); + lastStart = i; + lastVal = nbBit; + } + } else { + lastStart = i; + lastVal = nbBit; + startExists = true; + } + } else { + if ( startExists ) { + AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA); + } + startExists = false; + } + } + int chg = (i + 1 - theSt) & 7; + if ( chg == 0 ) { + if ( i < curMax ) { + c_full[0] = as[0]->fullB[(i + 1 - theSt) >> 3] | as[0]->partB[(i + 1 - theSt) >> 3]; + c_full[1] = as[1]->fullB[(i + 1 - theSt) >> 3] | as[1]->partB[(i + 1 - theSt) >> 3]; + c_full[2] = as[2]->fullB[(i + 1 - theSt) >> 3] | as[2]->partB[(i + 1 - theSt) >> 3]; + c_full[3] = as[3]->fullB[(i + 1 - theSt) >> 3] | as[3]->partB[(i + 1 - theSt) >> 3]; + } else { + // end of line. byebye + } + } else { + c_full[0] <<= 4; + c_full[1] <<= 4; + c_full[2] <<= 4; + c_full[3] <<= 4; + } + } + + } else { + + uint32_t c_full[16]; // we take nbSub < 16, since 16*16 supersampling makes a 1/256 precision in alpha values + // and that's the max of what 32bit argb can represent + // in fact, we'll treat it as 4*nbSub supersampling, so that's a half truth and a full lazyness from me + // uint32_t c_part[16]; + // start by putting the bits of the nbSub BitLignes in as[] in their respective c_full + + for (int i = 0; i < nbSub; i++) { + // fullB and partB treated equally + c_full[i] = as[i]->fullB[(curMin - theSt) >> 3] | as[i]->partB[(curMin - theSt) >> 3]; + c_full[i] <<= 4 * ((curMin - theSt) & 7); + /* c_part[i]=as[i]->partB[(curMin-theSt)>>3]; + c_part[i]<<=4*((curMin-theSt)&7);*/ + } + + spA = 1.0 / (4 * nbSub); // contribution to the alpha value of a single bit of the supersampled data + for (int i = curMin; i <= curMax;i++) { + int nbBit = 0; + // int nbPartBit=0; + // a little acceleration: if the lines only contain full or empty bits, we can flush + // what's remaining in the c_full at best we flush an entire c_full, ie 32 bits, or 32/4=8 pixels + bool allEmpty = true; + bool allFull = true; + for (int j = 0; j < nbSub; j++) { + if ( c_full[j] != 0 /*|| c_part[j] != 0*/ ) { + allEmpty=false; + break; + } + } + + if ( allEmpty ) { + // the remaining bits in c_full[] are empty: flush + if ( startExists ) { + AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA); + } + startExists = false; + i = theSt + (((i - theSt) & (~7) ) + 7); + } else { + for (int j = 0; j < nbSub; j++) { + if ( c_full[j] != 0xFFFFFFFF ) { + allFull=false; + break; + } + } + + if ( allFull ) { + // the remaining bits in c_full[] are empty: flush + if ( startExists ) { + if ( lastVal == 4 * nbSub) { + } else { + AddRun(lastStart, i, ((float) lastVal) * spA,((float) lastVal) * spA); + lastStart = i; + } + } else { + lastStart = i; + } + lastVal = 4 * nbSub; + startExists = true; + i = theSt + (((i - theSt) & (~7) ) + 7); + } else { + // alpha values will be between 0 and 1, so we have more work to do + // compute how many bit this pixel holds + for (int j = 0; j < nbSub; j++) { + nbBit += masks[c_full[j] >> 28]; +// nbPartBit+=masks[c_part[j]>>28]; + } + // and add a single-pixel run if needed, or extend the current run if the alpha value hasn't changed + if ( nbBit > 0 ) { + if ( startExists ) { + if ( lastVal == nbBit ) { + // alpha value hasn't changed: we continue + } else { + // alpha value did change: put the run that was being done,... + AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA); + // ... and start a new one + lastStart = i; + lastVal = nbBit; + } + } else { + // alpha value was 0, so we "create" a new run with alpha nbBit + lastStart = i; + lastVal = nbBit; + startExists = true; + } + } else { + if ( startExists ) { + AddRun(lastStart, i, ((float) lastVal) * spA,((float) lastVal) * spA); + } + startExists = false; + } + } + } + // move to the right: shift bits in the c_full[], and if we shifted everything, load the next c_full[] + int chg = (i + 1 - theSt) & 7; + if ( chg == 0 ) { + if ( i < curMax ) { + for (int j = 0; j < nbSub; j++) { + c_full[j] = as[j]->fullB[(i + 1 - theSt) >> 3] | as[j]->partB[(i + 1 - theSt) >> 3]; + // c_part[j]=as[j]->partB[(i+1-theSt)>>3]; + } + } else { + // end of line. byebye + } + } else { + for (int j = 0; j < nbSub; j++) { + c_full[j]<<=4; + // c_part[j]<<=4; + } + } + } + } + + if ( startExists ) { + AddRun(lastStart, curMax + 1, ((float) lastVal) * spA,((float) lastVal) * spA); + } +} + +/// Copy another IntLigne +void IntLigne::Copy(IntLigne *a) +{ + if ( a->nbRun <= 0 ) { + Reset(); + return; + } + + nbBord = 0; + nbRun = a->nbRun; + if ( nbRun > maxRun ) { + maxRun = nbRun; + runs = (int_ligne_run*) g_realloc(runs, maxRun * sizeof(int_ligne_run)); + } + memcpy(runs, a->runs, nbRun * sizeof(int_ligne_run)); +} + + +/** + * Copy a FloatLigne's runs. + * + * Compute non-overlapping runs with integer boundaries from a set of runs + * with floating-point boundaries. This involves replacing floating-point + * boundaries that are not integer by single-pixel runs, so this function + * contains plenty of rounding and float->integer conversion (read: + * time-consuming). + * + * \todo + * Optimization Questions: Why is this called so often compared with the + * other Copy() routines? How does AddRun() look for optimization potential? + */ +void IntLigne::Copy(FloatLigne* a) +{ + if ( a->runs.empty() ) { + Reset(); + return; + } + + /* if ( showCopy ) { + printf("\nfloatligne:\n"); + a->Affiche(); + }*/ + + nbBord = 0; + nbRun = 0; + firstAc = lastAc = -1; + bool pixExists = false; + int curPos = (int) floor(a->runs[0].st) - 1; + float lastSurf = 0; + float tolerance = 0.00001; + + // we take each run of the FloatLigne in sequence and make single-pixel runs of its boundaries as needed + // since the float_ligne_runs are non-overlapping, when a single-pixel run intersects with another runs, + // it must intersect with the single-pixel run created for the end of that run. so instead of creating a new + // int_ligne_run, we just add the coverage to that run. + for (auto & run : a->runs) { + float_ligne_run runA = run; + float curStF = floor(runA.st); + float curEnF = floor(runA.en); + int curSt = (int) curStF; + int curEn = (int) curEnF; + + // stEx: start boundary is not integer -> create single-pixel run for it + // enEx: end boundary is not integer -> create single-pixel run for it + // miEx: the runs minus the eventual single-pixel runs is not empty + bool stEx = true; + bool miEx = true; + bool enEx = true; + int miSt = curSt; + float miStF = curStF; + float msv; + float mev; + if ( runA.en - curEnF < tolerance ) { + enEx = false; + } + + // msv and mev are the start and end value of the middle section of the run, that is the run minus the + // single-pixel runs creaed for its boundaries + if ( runA.st-curStF < tolerance /*miSt == runA.st*/ ) { + stEx = false; + msv = runA.vst; + } else { + miSt += 1; + miStF += 1.0; + if ( enEx == false && miSt == curEn ) { + msv = runA.ven; + } else { + // msv=a->ValAt(miSt,runA.st,runA.en,runA.vst,runA.ven); + msv = runA.vst + (miStF-runA.st) * runA.pente; + } + } + + if ( miSt >= curEn ) { + miEx = false; + } + if ( stEx == false && miEx == false /*curEn == runA.st*/ ) { + mev = runA.vst; + } else if ( enEx == false /*curEn == runA.en*/ ) { + mev = runA.ven; + } else { + // mev=a->ValAt(curEn,runA.st,runA.en,runA.vst,runA.ven); + mev = runA.vst + (curEnF-runA.st) * runA.pente; + } + + // check the different cases + if ( stEx && enEx ) { + // stEx && enEx + if ( curEn > curSt ) { + if ( pixExists ) { + if ( curPos < curSt ) { + AddRun(curPos,curPos+1,lastSurf,lastSurf); + lastSurf=0.5*(msv+run.vst)*(miStF-run.st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } else { + lastSurf+=0.5*(msv+run.vst)*(miStF-run.st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } + pixExists=false; + } else { + lastSurf=0.5*(msv+run.vst)*(miStF-run.st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } + } else if ( pixExists ) { + if ( curPos < curSt ) { + AddRun(curPos,curPos+1,lastSurf,lastSurf); + lastSurf=0.5*(run.ven+run.vst)*(run.en-run.st); + curPos=curSt; + } else { + lastSurf += 0.5 * (run.ven+run.vst)*(run.en-run.st); + } + } else { + lastSurf=0.5*(run.ven+run.vst)*(run.en-run.st); + curPos=curSt; + pixExists=true; + } + } else if ( pixExists ) { + if ( curPos < curSt ) { + AddRun(curPos,curPos+1,lastSurf,lastSurf); + lastSurf = 0.5 * (msv+run.vst) * (miStF-run.st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } else { + lastSurf += 0.5 * (msv+run.vst) * (miStF-run.st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } + pixExists=false; + } else { + lastSurf = 0.5 * (msv+run.vst) * (miStF-run.st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } + if ( miEx ) { + if ( pixExists && curPos < miSt ) { + AddRun(curPos,curPos+1,lastSurf,lastSurf); + } + pixExists=false; + AddRun(miSt,curEn,msv,mev); + } + if ( enEx ) { + if ( curEn > curSt ) { + lastSurf=0.5*(mev+run.ven)*(run.en-curEnF); + pixExists=true; + curPos=curEn; + } else if ( ! stEx ) { + if ( pixExists ) { + AddRun(curPos,curPos+1,lastSurf,lastSurf); + } + lastSurf=0.5*(mev+run.ven)*(run.en-curEnF); + pixExists=true; + curPos=curEn; + } + } + } + if ( pixExists ) { + AddRun(curPos,curPos+1,lastSurf,lastSurf); + } + /* if ( showCopy ) { + printf("-> intligne:\n"); + Affiche(); + }*/ +} + + +void IntLigne::Enqueue(int no) +{ + if ( firstAc < 0 ) { + firstAc = lastAc = no; + bords[no].prev = bords[no].next = -1; + } else { + bords[no].next = -1; + bords[no].prev = lastAc; + bords[lastAc].next = no; + lastAc = no; + } +} + + +void IntLigne::Dequeue(int no) +{ + if ( no == firstAc ) { + if ( no == lastAc ) { + firstAc = lastAc = -1; + } else { + firstAc = bords[no].next; + } + } else if ( no == lastAc ) { + lastAc = bords[no].prev; + } else { + } + if ( bords[no].prev >= 0 ) { + bords[bords[no].prev].next = bords[no].next; + } + if ( bords[no].next >= 0 ) { + bords[bords[no].next].prev = bords[no].prev; + } + + bords[no].prev = bords[no].next = -1; +} + +/** + * Rasterization. + * + * The parameters have the same meaning as in the AlphaLigne class. + */ +void IntLigne::Raster(raster_info &dest, void *color, RasterInRunFunc worker) +{ + if ( nbRun <= 0 ) { + return; + } + + int min = runs[0].st; + int max = runs[nbRun-1].en; + if ( dest.endPix <= min || dest.startPix >= max ) { + return; + } + + int curRun = -1; + for (curRun = 0; curRun < nbRun; curRun++) { + if ( runs[curRun].en > dest.startPix ) { + break; + } + } + + if ( curRun >= nbRun ) { + return; + } + + if ( runs[curRun].st < dest.startPix ) { + int nst = runs[curRun].st; + int nen = runs[curRun].en; + float vst = runs[curRun].vst; + float ven = runs[curRun].ven; + float nvst = (vst * (nen - dest.startPix) + ven * (dest.startPix - nst)) / ((float) (nen - nst)); + if ( runs[curRun].en <= dest.endPix ) { + (worker)(dest, color, dest.startPix, nvst, runs[curRun].en, runs[curRun].ven); + } else { + float nven = (vst * (nen - dest.endPix) + ven * (dest.endPix - nst)) / ((float)(nen - nst)); + (worker)(dest, color, dest.startPix, nvst, dest.endPix, nven); + return; + } + curRun++; + } + + for (; (curRun < nbRun && runs[curRun].en <= dest.endPix); curRun++) { + (worker)(dest, color, runs[curRun].st, runs[curRun].vst, runs[curRun].en, runs[curRun].ven); +//Buffer::RasterRun(*dest,color,runs[curRun].st,runs[curRun].vst,runs[curRun].en,runs[curRun].ven); + } + + if ( curRun >= nbRun ) { + return; + } + + if ( runs[curRun].st < dest.endPix && runs[curRun].en > dest.endPix ) { + int const nst = runs[curRun].st; + int const nen = runs[curRun].en; + float const vst = runs[curRun].vst; + float const ven = runs[curRun].ven; + float const nven = (vst * (nen - dest.endPix) + ven * (dest.endPix - nst)) / ((float)(nen - nst)); + + (worker)(dest,color,runs[curRun].st,runs[curRun].vst,dest.endPix,nven); + curRun++; + } +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/livarot/int-line.h b/src/livarot/int-line.h new file mode 100644 index 0000000..576cfcf --- /dev/null +++ b/src/livarot/int-line.h @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef INKSCAPE_LIVAROT_INT_LINE_H +#define INKSCAPE_LIVAROT_INT_LINE_H + +#include "livarot/LivarotDefs.h" +#include "object/object-set.h" // For BooleanOp + +/** \file + * Coverage with integer boundaries. + */ + +class BitLigne; +class FloatLigne; + +/// A run with integer boundaries. +struct int_ligne_run { + int st; + int en; + float vst; + float ven; +}; + +/// Integer boundary. +struct int_ligne_bord { + int pos; + bool start; + float val; + int other; + int prev; + int next; +}; + +/** + * Coverage with integer boundaries. + * + * This is what we want for actual rasterization. It contains the same + * stuff as FloatLigne, but technically only the Copy() functions are used. + */ +class IntLigne { +public: + + int nbBord; + int maxBord; + int_ligne_bord* bords; + + int nbRun; + int maxRun; + int_ligne_run* runs; + + int firstAc; + int lastAc; + + IntLigne(); + virtual ~IntLigne(); + + void Reset(); + int AddBord(int spos, float sval, int epos, float eval); + + void Flatten(); + + void Affiche(); + + int AddRun(int st, int en, float vst, float ven); + + void Booleen(IntLigne* a, IntLigne* b, BooleanOp mod); + + void Copy(IntLigne* a); + void Copy(FloatLigne* a); + void Copy(BitLigne* a); + void Copy(int nbSub,BitLigne **a); + + void Enqueue(int no); + void Dequeue(int no); + float RemainingValAt(int at); + + static int CmpBord(void const *p1, void const *p2) { + int_ligne_bord const *d1 = reinterpret_cast<int_ligne_bord const *>(p1); + int_ligne_bord const *d2 = reinterpret_cast<int_ligne_bord const *>(p2); + + if ( d1->pos == d2->pos ) { + if ( d1->start && !(d2->start) ) { + return 1; + } + if ( !(d1->start) && d2->start ) { + return -1; + } + return 0; + } + return (( d1->pos < d2->pos ) ? -1 : 1); + }; + + inline float ValAt(int at, int ps, int pe, float vs, float ve) { + return ((at - ps) * ve + (pe - at) * vs) / (pe - ps); + }; + + void Raster(raster_info &dest, void *color, RasterInRunFunc worker); +}; + + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/path-description.cpp b/src/livarot/path-description.cpp new file mode 100644 index 0000000..0da2d0a --- /dev/null +++ b/src/livarot/path-description.cpp @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2011 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include "livarot/path-description.h" +#include <2geom/affine.h> + +PathDescr *PathDescrMoveTo::clone() const +{ + return new PathDescrMoveTo(*this); +} + +void PathDescrMoveTo::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &/*last*/) const +{ + s << "M " << p[Geom::X] << " " << p[Geom::Y] << " "; +} + +void PathDescrMoveTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " m " << p[Geom::X] << " " << p[Geom::Y]; +} + +void PathDescrLineTo::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &/*last*/) const +{ + s << "L " << p[Geom::X] << " " << p[Geom::Y] << " "; +} + +PathDescr *PathDescrLineTo::clone() const +{ + return new PathDescrLineTo(*this); +} + +void PathDescrLineTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " l " << p[Geom::X] << " " << p[Geom::Y]; +} + +PathDescr *PathDescrBezierTo::clone() const +{ + return new PathDescrBezierTo(*this); +} + +void PathDescrBezierTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " b " << p[Geom::X] << " " << p[Geom::Y] << " " << nb; +} + +PathDescr *PathDescrIntermBezierTo::clone() const +{ + return new PathDescrIntermBezierTo(*this); +} + +void PathDescrIntermBezierTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " i " << p[Geom::X] << " " << p[Geom::Y]; +} + +void PathDescrCubicTo::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &last) const +{ + // To see why these weird factors of 3 exist here, refer to the documentation in the header file. + s << "C " + << last[Geom::X] + start[0] / 3 << " " + << last[Geom::Y] + start[1] / 3 << " " + << p[Geom::X] - end[0] / 3 << " " + << p[Geom::Y] - end[1] / 3 << " " + << p[Geom::X] << " " + << p[Geom::Y] << " "; +} + +PathDescr *PathDescrCubicTo::clone() const +{ + return new PathDescrCubicTo(*this); +} + +void PathDescrCubicTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " c " + << p[Geom::X] << " " << p[Geom::Y] << " " + << start[Geom::X] << " " << start[Geom::Y] << " " + << end[Geom::X] << " " << end[Geom::Y] << " "; +} + +void PathDescrArcTo::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &/*last*/) const +{ + s << "A " + << rx << " " + << ry << " " + << angle << " " + << (large ? "1" : "0") << " " + << (clockwise ? "0" : "1") << " " + << p[Geom::X] << " " + << p[Geom::Y] << " "; +} + +PathDescr *PathDescrArcTo::clone() const +{ + return new PathDescrArcTo(*this); +} + +void PathDescrArcTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " a " + << p[Geom::X] << " " << p[Geom::Y] << " " + << rx << " " << ry << " " + << angle << " " + << (clockwise ? 1 : 0) << " " + << (large ? 1 : 0); +} + +PathDescr *PathDescrForced::clone() const +{ + return new PathDescrForced(*this); +} + +void PathDescrClose::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &/*last*/) const +{ + s << "z "; +} + +PathDescr *PathDescrClose::clone() const +{ + return new PathDescrClose(*this); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/livarot/path-description.h b/src/livarot/path-description.h new file mode 100644 index 0000000..57c9eb1 --- /dev/null +++ b/src/livarot/path-description.h @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_INKSCAPE_LIVAROT_PATH_DESCRIPTION_H +#define SEEN_INKSCAPE_LIVAROT_PATH_DESCRIPTION_H + +#include <2geom/point.h> +#include "svg/stringstream.h" + +// path description commands +/* FIXME: these should be unnecessary once the refactoring of the path +** description stuff is finished. +*/ + +/** + * An enum to store the path command type. A number is assigned to each type and the + * PathDescr class stores a flag variable that stores the number to know which type it is. + */ +enum +{ + descr_moveto = 0, /*!< A MoveTo path command */ + descr_lineto = 1, /*!< A LineTo path command */ + descr_cubicto = 2, /*!< A CubicTo path command. Basically a cubic Bezier */ + descr_bezierto = 3, /*!< A BezierTo path command is a quadratic Bezier spline. It can contain as many control points as you want to + add. The BezierTo instruction only stores the final point and the total number of control points. The actual + control points are stored in descr_interm_bezier instructions. One for each control point. */ + descr_arcto = 4, /*!< An elliptical arc */ + descr_close = 5, /*!< A close path command */ + descr_interm_bezier = 6, /*!< Control point for the last BezierTo instruction */ + descr_forced = 7, /*!< Not exactly sure what a forced point means. As far as I have seen in the simplify code, a forced + point is preferred to be kept. The simplification algorithm would make sure the forced point makes + its way to the final result. However, as far as I can see, forced point stuff is not used in Inkscape. + TODO: Explore how forced points might be being used when Simplify is done after Boolean Ops */ + descr_type_mask = 15 /*!< As mentioned above, the flag variable of PathDescr stores the type of the command using this enum. The higher bits + (after the first four (bit 0 to bit 3) could be used for other flag stuff, so this descr_type_mask can be used to just + extract the type. 15 in HEX is 0xF and in BIN is 0000 1111 so ANDing with it will zero any higher bits just leaving you + with the type. */ +}; + +/** + * A base class for Livarot's path commands. Each curve type such as Line, CubicBezier + * derives from this base class. + */ +struct PathDescr +{ + PathDescr() : flags(0), associated(-1), tSt(0), tEn(1) {} + PathDescr(int f) : flags(f), associated(-1), tSt(0), tEn(1) {} + virtual ~PathDescr() = default; + + int getType() const { return flags & descr_type_mask; } + void setType(int t) { + flags &= ~descr_type_mask; + flags |= t; + } + + /** + * A virtual function that derived classes will implement. Dumps the SVG path d attribute + * for this path description. + * + * @param s The stream to put the SVG description in. + * @param last The last point before this path description. This is needed for the computation + * of SVG descriptions of instructions such as Cubic and Arc. + */ + virtual void dumpSVG(Inkscape::SVGOStringStream &/*s*/, Geom::Point const &/*last*/) const {} + + /** + * A virtual function that derived classes will implement. Returns a newly allocated copy + * of the path description. + */ + virtual PathDescr *clone() const = 0; + + /** + * A virtual function that derived classes will implement. Similar to dumpSVG however this + * prints a simpler path description that's not SVG, only used for debugging purposes. Maybe + * the motivation was to support instructions such as BezierTo and IntermBezierTo which do + * not have SVG path description equivalents. + * + * @param s The stream to print to. + */ + virtual void dump(std::ostream &/*s*/) const {} + + int flags; /*!< Lower 4 bits contain the type of the path description as decided by the enum + above, upper bits could contain other information but don't know if they really do at all */ + int associated; /*!< Index of the last polyline point associated with this path description. Interestingly, Path::ConvertWithBackData + doesn't set this field at all while Path::Convert and Path::ConvertEvenLines do. */ + double tSt; /*!< By default set to 0. No idea if this is used at all. TODO */ + double tEn; /*!< By default set to 1. No idea if this is used at all. TODO */ +}; + +/** + * A MoveTo path command. + */ +struct PathDescrMoveTo : public PathDescr +{ + PathDescrMoveTo(Geom::Point const &pp) + : PathDescr(descr_moveto), p(pp) {} + + void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override; + PathDescr *clone() const override; + void dump(std::ostream &s) const override; + + Geom::Point p; /*!< The point to move to. */ +}; + +/** + * A LineTo path command. + */ +struct PathDescrLineTo : public PathDescr +{ + PathDescrLineTo(Geom::Point const &pp) + : PathDescr(descr_lineto), p(pp) {} + + void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override; + PathDescr *clone() const override; + void dump(std::ostream &s) const override; + + Geom::Point p; /*!< The point to draw a line to. */ +}; + +// quadratic bezier curves: a set of control points, and an endpoint + +/** + * A quadratic bezier spline + * + * Stores the final point as well as the total number of control points. The control + * points will exist in the path commands following this one which would be of the type + * PathDescrIntermBezierTo. + */ +struct PathDescrBezierTo : public PathDescr +{ + PathDescrBezierTo(Geom::Point const &pp, int n) + : PathDescr(descr_bezierto), p(pp), nb(n) {} + + PathDescr *clone() const override; + void dump(std::ostream &s) const override; + + Geom::Point p; /*!< The final point of the quadratic Bezier spline. */ + int nb; /*!< The total number of control points. The path commands following this one of the type PathDescrIntermBezierTo + will store these control points, one in each one. */ +}; + +/* FIXME: I don't think this should be necessary */ + +/** + * Intermediate quadratic Bezier spline command. + * + * These store the control points needed by the PathDescrBezierTo instruction. + */ +struct PathDescrIntermBezierTo : public PathDescr +{ + PathDescrIntermBezierTo() + : PathDescr(descr_interm_bezier) , p(0, 0) {} + PathDescrIntermBezierTo(Geom::Point const &pp) + : PathDescr(descr_interm_bezier), p(pp) {} + + PathDescr *clone() const override; + void dump(std::ostream &s) const override; + + Geom::Point p; /*!< The control point. */ +}; + +/** + * Cubic Bezier path command. + * + * There is something funny about this one. A typical BezierCurve consists of points + * p0, p1, p2, p3 where p1 and p2 are control points. This is a command so it's + * quite expected that p0 is not needed. What's interesting is that instead of storing + * p1 and p2, (p1 - p0) * 3 and (p3 - p2) * 3 are stored in start and end respectively. + * I can't see a good reason for why this was done. Because of this, there is additional + * mess required in the formulas for bezier curve splitting. + */ +struct PathDescrCubicTo : public PathDescr +{ + PathDescrCubicTo(Geom::Point const &pp, Geom::Point const &s, Geom::Point const& e) + : PathDescr(descr_cubicto), p(pp), start(s), end(e) {} + + void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override; + PathDescr *clone() const override; + void dump(std::ostream &s) const override; + + Geom::Point p; /*!< The final point of the bezier curve. */ + Geom::Point start; /*!< 3 * (p1 - p0) where p0 is the start point of the cubic bezier and + p1 is the first control point. */ + Geom::Point end; /*!< 3 * (p3 - p2) where p3 is the final point of the cubic bezier and + p2 is the second control point. */ +}; + +/** + * Elliptical Arc path command. + * + * Exactly the equivalent of an SVG elliptical arc description. + */ +struct PathDescrArcTo : public PathDescr +{ + PathDescrArcTo(Geom::Point const &pp, double x, double y, double a, bool l, bool c) + : PathDescr(descr_arcto), p(pp), rx(x), ry(y), angle(a), large(l), clockwise(c) {} + + void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override; + PathDescr *clone() const override; + void dump(std::ostream &s) const override; + + Geom::Point p; /*!< The final point of the arc. */ + double rx; /*!< The radius in the x direction. */ + double ry; /*!< The radius in the y direction. */ + double angle; /*!< The angle it makes with the x axis in degrees. TODO confirm that */ + bool large; /*!< The large arc or the small one? */ + bool clockwise; /*!< Clockwise arc or anti-clockwise one? */ +}; + +/** + * A forced point path command. + * + * TODO: This needs more research. Why is this useful and where? + */ +struct PathDescrForced : public PathDescr +{ + PathDescrForced() : PathDescr(descr_forced), p(0, 0) {} + + PathDescr *clone() const override; + + /* FIXME: not sure whether _forced should have a point associated with it; + ** Path::ConvertForcedToMoveTo suggests that maybe it should. + */ + Geom::Point p; /*!< The forced point itself? */ +}; + + +/** + * Close Path instruction. + */ +struct PathDescrClose : public PathDescr +{ + PathDescrClose() : PathDescr(descr_close) {} + + void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override; + PathDescr *clone() const override; + + /* FIXME: not sure whether _forced should have a point associated with it; + ** Path::ConvertForcedToMoveTo suggests that maybe it should. + */ + Geom::Point p; /*!< Useless since close instruction needs no point. */ +}; + +#endif + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-event-queue.h b/src/livarot/sweep-event-queue.h new file mode 100644 index 0000000..f0fb479 --- /dev/null +++ b/src/livarot/sweep-event-queue.h @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A container of intersection events. + *//* + * Authors: see git history + * + * Copyright (C) 2010 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_LIVAROT_SWEEP_EVENT_QUEUE_H +#define SEEN_LIVAROT_SWEEP_EVENT_QUEUE_H + +#include <2geom/forward.h> +class SweepEvent; +class SweepTree; + + +/** + * The structure to hold the intersections events encountered during the sweep. It's an array of + * SweepEvent (not allocated with "new SweepEvent[n]" but with a malloc). There's a list of + * indices because it's a binary heap: inds[i] tell that events[inds[i]] has position i in the + * heap. Each SweepEvent has a field to store its index in the heap, too. + */ +class SweepEventQueue +{ +public: + SweepEventQueue(int s); + virtual ~SweepEventQueue(); + + /** + * Number of events currently stored. + * + * @return The number of elements stored. + */ + int size() const { return nbEvt; } + + /** Look for the top most intersection in the heap + * + * @param iLeft Reference that function will set to the left node of top most intersection. + * @param iRight Reference that function will set to the right node of top most intersection. + * @param oPt Reference that function will set to the intersection point of top most intersection. + * @param itl Reference that function will set to time of top most intersection on the left edge. + * @param itr Reference that function will set to time of top most intersection on the right edge. + * + * @return True if an intersection event exists false otherwise. + */ + bool peek(SweepTree * &iLeft, SweepTree * &iRight, Geom::Point &oPt, double &itl, double &itr); + + /** Extract the top most intersection from the heap + * + * @param iLeft Reference that function will set to the left node of top most intersection. + * @param iRight Reference that function will set to the right node of top most intersection. + * @param oPt Reference that function will set to the point of top most intersection. + * @param itl Reference that function will set to time of top most intersection on the left edge. + * @param itr Reference that function will set to time of top most intersection on the right edge. + * + * @return True if an intersection event exists false otherwise. + */ + bool extract(SweepTree * &iLeft, SweepTree * &iRight, Geom::Point &oPt, double &itl, double &itr); + + /** Add an intersection in the binary heap + * + * @param iLeft Pointer to left node of intersection. + * @param iRight Pointer to right node of intersection. + * @param iPt Point of intersection. + * @param itl Time of intersection on the left edge. + * @param itr Time of intersection on the right edge. + */ + SweepEvent *add(SweepTree *iLeft, SweepTree *iRight, Geom::Point &iPt, double itl, double itr); + + /** + * Remove event from the event queue. Make sure to clear the evt pointers from the nodes + * involved. + * + * @param e The event to remove. + */ + void remove(SweepEvent *e); + + /** + * Relocate the event `e` to the location to. + * + * This will place all data of `e` in `to` and also update any + * evt pointers held by the intersection nodes. + * + * @param e The SweepEvent to relocate. + * @param to The index of the location where we want to relocate `e` to. + */ + void relocate(SweepEvent *e, int to); + +private: + int nbEvt; /*!< Number of events currently in the heap. */ + int maxEvt; /*!< Allocated size of the heap. */ + int *inds; /*!< Indices. */ + SweepEvent *events; /*!< Sweep events. */ +}; + +#endif /* !SEEN_LIVAROT_SWEEP_EVENT_QUEUE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-event.cpp b/src/livarot/sweep-event.cpp new file mode 100644 index 0000000..bb9e4e7 --- /dev/null +++ b/src/livarot/sweep-event.cpp @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include <glib.h> +#include "livarot/sweep-event-queue.h" +#include "livarot/sweep-tree.h" +#include "livarot/sweep-event.h" +#include "livarot/Shape.h" + +SweepEventQueue::SweepEventQueue(int s) : nbEvt(0), maxEvt(s) +{ + /* FIXME: use new[] for this, but this causes problems when delete[] + ** calls the SweepEvent destructors. + */ + events = (SweepEvent *) g_malloc(maxEvt * sizeof(SweepEvent)); + inds = new int[maxEvt]; +} + +SweepEventQueue::~SweepEventQueue() +{ + g_free(events); + delete []inds; +} + +SweepEvent *SweepEventQueue::add(SweepTree *iLeft, SweepTree *iRight, Geom::Point &px, double itl, double itr) +{ + if (nbEvt > maxEvt) { + return nullptr; + } + + int const n = nbEvt++; + events[n].MakeNew (iLeft, iRight, px, itl, itr); + + SweepTree *t[2] = { iLeft, iRight }; + for (auto & i : t) { + Shape *s = i->src; + Shape::dg_arete const &e = s->getEdge(i->bord); + int const n = std::max(e.st, e.en); + s->pData[n].pending++;; + } + + events[n].ind = n; + inds[n] = n; + + int curInd = n; + while (curInd > 0) { + int const half = (curInd - 1) / 2; + int const no = inds[half]; + if (px[1] < events[no].posx[1] + || (px[1] == events[no].posx[1] && px[0] < events[no].posx[0])) + { + events[n].ind = half; + events[no].ind = curInd; + inds[half] = n; + inds[curInd] = no; + } else { + break; + } + + curInd = half; + } + + return events + n; +} + + + +bool SweepEventQueue::peek(SweepTree * &iLeft, SweepTree * &iRight, Geom::Point &px, double &itl, double &itr) +{ + if (nbEvt <= 0) { + return false; + } + + SweepEvent const &e = events[inds[0]]; + + iLeft = e.sweep[LEFT]; + iRight = e.sweep[RIGHT]; + px = e.posx; + itl = e.tl; + itr = e.tr; + + return true; +} + +bool SweepEventQueue::extract(SweepTree * &iLeft, SweepTree * &iRight, Geom::Point &px, double &itl, double &itr) +{ + if (nbEvt <= 0) { + return false; + } + + SweepEvent &e = events[inds[0]]; + + iLeft = e.sweep[LEFT]; + iRight = e.sweep[RIGHT]; + px = e.posx; + itl = e.tl; + itr = e.tr; + remove(&e); + + return true; +} + + +void SweepEventQueue::remove(SweepEvent *e) +{ + if (nbEvt <= 1) { + e->MakeDelete (); + nbEvt = 0; + return; + } + + int const n = e->ind; + int to = inds[n]; + e->MakeDelete(); + relocate(&events[--nbEvt], to); + + int const moveInd = nbEvt; + if (moveInd == n) { + return; + } + + to = inds[moveInd]; + + events[to].ind = n; + inds[n] = to; + + int curInd = n; + Geom::Point const px = events[to].posx; + bool didClimb = false; + while (curInd > 0) { + int const half = (curInd - 1) / 2; + int const no = inds[half]; + if (px[1] < events[no].posx[1] + || (px[1] == events[no].posx[1] && px[0] < events[no].posx[0])) + { + events[to].ind = half; + events[no].ind = curInd; + inds[half] = to; + inds[curInd] = no; + didClimb = true; + } else { + break; + } + curInd = half; + } + + if (didClimb) { + return; + } + + while (2 * curInd + 1 < nbEvt) { + int const child1 = 2 * curInd + 1; + int const child2 = child1 + 1; + int const no1 = inds[child1]; + int const no2 = inds[child2]; + if (child2 < nbEvt) { + if (px[1] > events[no1].posx[1] + || (px[1] == events[no1].posx[1] + && px[0] > events[no1].posx[0])) + { + if (events[no2].posx[1] > events[no1].posx[1] + || (events[no2].posx[1] == events[no1].posx[1] + && events[no2].posx[0] > events[no1].posx[0])) + { + events[to].ind = child1; + events[no1].ind = curInd; + inds[child1] = to; + inds[curInd] = no1; + curInd = child1; + } else { + events[to].ind = child2; + events[no2].ind = curInd; + inds[child2] = to; + inds[curInd] = no2; + curInd = child2; + } + } else { + if (px[1] > events[no2].posx[1] + || (px[1] == events[no2].posx[1] + && px[0] > events[no2].posx[0])) + { + events[to].ind = child2; + events[no2].ind = curInd; + inds[child2] = to; + inds[curInd] = no2; + curInd = child2; + } else { + break; + } + } + } else { + if (px[1] > events[no1].posx[1] + || (px[1] == events[no1].posx[1] + && px[0] > events[no1].posx[0])) + { + events[to].ind = child1; + events[no1].ind = curInd; + inds[child1] = to; + inds[curInd] = no1; + } + + break; + } + } +} + + + + +void SweepEventQueue::relocate(SweepEvent *e, int to) +{ + if (inds[e->ind] == to) { + return; // j'y suis deja + } + + events[to] = *e; + + e->sweep[LEFT]->evt[RIGHT] = events + to; + e->sweep[RIGHT]->evt[LEFT] = events + to; + inds[e->ind] = to; +} + + +/* + * a simple binary heap + * it only contains intersection events + * the regular benley-ottman stuffs the segment ends in it too, but that not needed here since theses points + * are already sorted. and the binary heap is much faster with only intersections... + * the code sample on which this code is based comes from purists.org + */ +SweepEvent::SweepEvent() +{ + MakeNew (nullptr, nullptr, Geom::Point(0, 0), 0, 0); +} + +SweepEvent::~SweepEvent() +{ + MakeDelete(); +} + +void SweepEvent::MakeNew(SweepTree *iLeft, SweepTree *iRight, Geom::Point const &px, double itl, double itr) +{ + ind = -1; + posx = px; + tl = itl; + tr = itr; + sweep[LEFT] = iLeft; + sweep[RIGHT] = iRight; + sweep[LEFT]->evt[RIGHT] = this; + sweep[RIGHT]->evt[LEFT] = this; +} + +void SweepEvent::MakeDelete() +{ + for (int i = 0; i < 2; i++) { + if (sweep[i]) { + Shape *s = sweep[i]->src; + Shape::dg_arete const &e = s->getEdge(sweep[i]->bord); + int const n = std::max(e.st, e.en); + s->pData[n].pending--; + } + + sweep[i]->evt[1 - i] = nullptr; + sweep[i] = nullptr; + } +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-event.h b/src/livarot/sweep-event.h new file mode 100644 index 0000000..2776c7a --- /dev/null +++ b/src/livarot/sweep-event.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef INKSCAPE_LIVAROT_SWEEP_EVENT_H +#define INKSCAPE_LIVAROT_SWEEP_EVENT_H +/** \file + * Intersection events. + */ + +#include <2geom/point.h> +class SweepTree; + + +/** + * An intersection event structure to record any intersections that are + * detected (predicted) during the sweepline. + */ +class SweepEvent +{ +public: + SweepTree *sweep[2]; /*!< Nodes associated with the left and right edge of the intersection. */ + + Geom::Point posx; /*!< Point of the intersection. */ + double tl; /*!< Time value of the intersection on the left edge (tl).*/ + double tr; /*!< Time value of the intersection on the right edge (tr). */ + + int ind; /*!< Index in the binary heap. */ + + SweepEvent(); // not used. + virtual ~SweepEvent(); // not used. + + /** + * Initialize the sweep event. + * + * @param iLeft The left node of the intersection. + * @param iRight The right node of the intersection. + * @param iPt The intersection point. + * @param itl The time value of the intersection on the left edge. + * @param itr The time value of the intersection on the right edge. + */ + void MakeNew (SweepTree * iLeft, SweepTree * iRight, Geom::Point const &iPt, + double itl, double itr); + + /// Void a SweepEvent structure. + + /** + * Empty the sweep event data. + * + * Also reset event pointers of any SweepTree nodes that might point to this event. + */ + void MakeDelete (); +}; + + +#endif /* !INKSCAPE_LIVAROT_SWEEP_EVENT_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-tree-list.cpp b/src/livarot/sweep-tree-list.cpp new file mode 100644 index 0000000..97640fd --- /dev/null +++ b/src/livarot/sweep-tree-list.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include <glib.h> +#include "livarot/sweep-tree.h" +#include "livarot/sweep-tree-list.h" + + +SweepTreeList::SweepTreeList(int s) : + nbTree(0), + maxTree(s), + trees((SweepTree *) g_malloc(s * sizeof(SweepTree))), + racine(nullptr) +{ + /* FIXME: Use new[] for trees initializer above, but watch out for bad things happening when + * SweepTree::~SweepTree is called. + */ +} + + +SweepTreeList::~SweepTreeList() +{ + g_free(trees); + trees = nullptr; +} + + +SweepTree *SweepTreeList::add(Shape *iSrc, int iBord, int iWeight, int iStartPoint, Shape */*iDst*/) +{ + if (nbTree >= maxTree) { + return nullptr; + } + + int const n = nbTree++; + trees[n].MakeNew(iSrc, iBord, iWeight, iStartPoint); + + return trees + n; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-tree-list.h b/src/livarot/sweep-tree-list.h new file mode 100644 index 0000000..25c350c --- /dev/null +++ b/src/livarot/sweep-tree-list.h @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2010 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/** @file + * @brief SweepTreeList definition + */ + +#ifndef INKSCAPE_LIVAROT_SWEEP_TREE_LIST_H +#define INKSCAPE_LIVAROT_SWEEP_TREE_LIST_H + +class Shape; +class SweepTree; + +/** + * The sweepline tree to store a linear sequence of edges that intersect with the sweepline + * in the exact order. + * + * This could just be a double-linked list but it also an AVL search tree + * to quickly find edges. + * + * In this documentation, a SweepTree instance is referred to as a node. + * + * This is a class to store the nodes. Most interesting stuff happens in the class + * SweepTree or its parent class AVLTree.h This just keeps the list of nodes and the pointer + * to the root node. + */ +class SweepTreeList { +public: + int nbTree; /*!< Number of nodes in the tree. */ + int const maxTree; /*!< Max number of nodes in the tree. */ + SweepTree *trees; /*!< The array of nodes. */ + SweepTree *racine; /*!< Root of the tree. */ + + /** + * Constructor to create a new SweepTreeList. + * + * @param s The number of maximum nodes it should be able to hold. + */ + SweepTreeList(int s); + + /** + * The destructor. But didn't have to be virtual. + */ + virtual ~SweepTreeList(); + + /** + * Create a new node and add it. This doesn't do any insertion in tree though. It just + * creates the node and puts it in the list of nodes. The actual insertion would need + * to be done by calling SweepTree::Insert or in the speical case SweepTree::InsertAt. + * + * @param iSrc A pointer to the shape. + * @param iBord The edge index. + * @param iWeight Weight of the of the edge. Weight of 2 is equivalent of two identical edges + * with same direction on top of each other. + * @param iStartPoint The point at which this node got added. (the upper endpoint if sweeping + * top to bottom). + * @param iDst Supposed to be the destination shape. The Shape on which Shape::ConvertToShape + * was called. `iSrc` is the parameter that was passed in Shape::ConvertToShape. Useless + * parameter though, not used. + * @param The address of the newly added node. Can be used later for Inserting it or anything + * else. + */ + SweepTree *add(Shape *iSrc, int iBord, int iWeight, int iStartPoint, Shape *iDst); +}; + + +#endif /* !INKSCAPE_LIVAROT_SWEEP_TREE_LIST_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-tree.cpp b/src/livarot/sweep-tree.cpp new file mode 100644 index 0000000..4536ae3 --- /dev/null +++ b/src/livarot/sweep-tree.cpp @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include "livarot/sweep-event-queue.h" +#include "livarot/sweep-tree-list.h" +#include "livarot/sweep-tree.h" +#include "livarot/sweep-event.h" +#include "livarot/Shape.h" + + +/* + * the AVL tree holding the edges intersecting the sweepline + * that structure is very sensitive to anything + * you have edges stored in nodes, the nodes are sorted in increasing x-order of intersection + * with the sweepline, you have the 2 potential intersections of the edge in the node with its + * neighbours, plus the fact that it's stored in an array that's realloc'd + */ + +SweepTree::SweepTree() +{ + src = nullptr; + bord = -1; + startPoint = -1; + evt[LEFT] = evt[RIGHT] = nullptr; + sens = true; + //invDirLength=1; +} + +SweepTree::~SweepTree() +{ + MakeDelete(); +} + +void +SweepTree::MakeNew(Shape *iSrc, int iBord, int iWeight, int iStartPoint) +{ + AVLTree::MakeNew(); + ConvertTo(iSrc, iBord, iWeight, iStartPoint); +} + +void +SweepTree::ConvertTo(Shape *iSrc, int iBord, int iWeight, int iStartPoint) +{ + src = iSrc; + bord = iBord; + evt[LEFT] = evt[RIGHT] = nullptr; + startPoint = iStartPoint; + if (src->getEdge(bord).st < src->getEdge(bord).en) { + if (iWeight >= 0) + sens = true; + else + sens = false; + } else { + if (iWeight >= 0) + sens = false; + else + sens = true; + } + //invDirLength=src->eData[bord].isqlength; + //invDirLength=1/sqrt(src->getEdge(bord).dx*src->getEdge(bord).dx+src->getEdge(bord).dy*src->getEdge(bord).dy); +} + + +void SweepTree::MakeDelete() +{ + for (int i = 0; i < 2; i++) { + if (evt[i]) { + evt[i]->sweep[1 - i] = nullptr; + } + evt[i] = nullptr; + } + + AVLTree::MakeDelete(); +} + + +// find the position at which node "newOne" should be inserted in the subtree rooted here +// we want to order with respect to the order of intersections with the sweepline, currently +// lying at y=px[1]. +// px is the upper endpoint of newOne +int +SweepTree::Find(Geom::Point const &px, SweepTree *newOne, SweepTree *&insertL, + SweepTree *&insertR, bool sweepSens) +{ + // get the edge associated with this node: one point+one direction + // since we're dealing with line, the direction (bNorm) is taken downwards + Geom::Point bOrig, bNorm; + bOrig = src->pData[src->getEdge(bord).st].rx; + bNorm = src->eData[bord].rdx; + if (src->getEdge(bord).st > src->getEdge(bord).en) { + bNorm = -bNorm; + } + // rotate to get the normal to the edge + bNorm=bNorm.ccw(); + + Geom::Point diff; + diff = px - bOrig; + + // compute (px-orig)^dir to know on which side of this edge the point px lies + double y = 0; + //if ( startPoint == newOne->startPoint ) { + // y=0; + //} else { + y = dot(bNorm, diff); + //} + //y*=invDirLength; + if (fabs(y) < 0.000001) { + // that damn point px lies on me, so i need to consider to direction of the edge in + // newOne to know if it goes toward my left side or my right side + // sweepSens is needed (actually only used by the Scan() functions) because if the sweepline goes upward, + // signs change + // prendre en compte les directions + Geom::Point nNorm; + nNorm = newOne->src->eData[newOne->bord].rdx; + if (newOne->src->getEdge(newOne->bord).st > + newOne->src->getEdge(newOne->bord).en) + { + nNorm = -nNorm; + } + nNorm=nNorm.ccw(); + + if (sweepSens) { + y = cross(bNorm, nNorm); + } else { + y = cross(nNorm, bNorm); + } + if (y == 0) { + y = dot(bNorm, nNorm); + if (y == 0) { + insertL = this; + insertR = static_cast<SweepTree *>(elem[RIGHT]); + return found_exact; + } + } + } + if (y < 0) { + if (child[LEFT]) { + return (static_cast<SweepTree *>(child[LEFT]))->Find(px, newOne, + insertL, insertR, + sweepSens); + } else { + insertR = this; + insertL = static_cast<SweepTree *>(elem[LEFT]); + if (insertL) { + return found_between; + } else { + return found_on_left; + } + } + } else { + if (child[RIGHT]) { + return (static_cast<SweepTree *>(child[RIGHT]))->Find(px, newOne, + insertL, insertR, + sweepSens); + } else { + insertL = this; + insertR = static_cast<SweepTree *>(elem[RIGHT]); + if (insertR) { + return found_between; + } else { + return found_on_right; + } + } + } + return not_found; +} + +// only find a point's position +int +SweepTree::Find(Geom::Point const &px, SweepTree * &insertL, + SweepTree * &insertR) +{ + Geom::Point bOrig, bNorm; + // The start point of the original edge vector + bOrig = src->pData[src->getEdge(bord).st].rx; + // The edge vector + bNorm = src->eData[bord].rdx; + // Flip the edge vector if it's bottom to top or horizontal and right to left + if (src->getEdge(bord).st > src->getEdge(bord).en) + { + bNorm = -bNorm; + } + // rotate the edge vector counter clockwise by 90 degrees + bNorm=bNorm.ccw(); + + // draw a vector from the start point to the actual point + Geom::Point diff; + diff = px - bOrig; + + double y = 0; + y = dot(bNorm, diff); // take the dot product + // ANALOGY FROM DIAGRAM (See doc in header file): + // this case is the same as if we are at node (15) and want to add (15). Usually, you can't + // add same stuff in a binary search tree but here it's fine to do so (I guess) + // In that case, we have found an exact match and the point belongs between 15 and the one + // on it's right (16). + if (y == 0) // point lies on the edge (or at least the line of the edge) + { + insertL = this; + insertR = static_cast<SweepTree *>(elem[RIGHT]); + return found_exact; + } + + // ANALOGY FROM DIAGRAM (See doc in header file): + // This is the same as inserting 3 while standing at 10, or inserting 3 while at 5 or inserting + // 13 while at 15 or inserting 1 while at 2 or inserting 11 while at 12. + if (y < 0) // lies to the left of the edge + { + if (child[LEFT]) // is there child on left? This is true at 10, 5, 15 but not at the nodes such as 2, 6, 12, 16 + { + // if there is a child on left, let the child do the searching + return (static_cast<SweepTree *>(child[LEFT]))->Find(px, insertL, // if yes, let that child do the finding now + insertR); + } + else // no child on the left? Means either a leaf node or node has no child on left. + { + // well we are sure that there is no child on the left, which means this new node goes + // to the left of this node, but that doesn't really mean there no left element in the + // linked list, there sure can be. For example, if you're inserting 11 while standing + // at 12, there is no left child, but in the linked list, there is 10 to the left. + insertR = this; + insertL = static_cast<SweepTree *>(elem[LEFT]); + if (insertL) + { + return found_between; + } + else // however, if you're at 2 and inserting 1, there is no left child, but there is also nothing on the left in the linked list either + { + return found_on_left; + } + } + } // lies to the right of the edge + // ANALOGY FROM DIAGRAM (See doc in header file): + // This is the same as inserting 14 while standing at 10, 7 while standing at 5, 18 while + // standing at 15, 7 while standing at 6, you get the point + else + { + if (child[RIGHT]) // is there a child to the right? If you're at 10 or 5 or 15 there is child on right so you let the child decide where + { // new node goes but not if you're at leaf nodes such as 2, 6, 12, 16 or any other node that doesn't have a right child + return (static_cast<SweepTree *>(child[RIGHT]))->Find(px, insertL, // let that child do the finding now + insertR); + } + else + { + // okay so no right child, but stil you can have an element to the right in the linked + // list. For example you are at 6 and want to insert 7, no child on the right so we are + // sure the new node goes to the right of 6 but there is still 10 to the right in the + // double-linked list. + insertL = this; + insertR = static_cast<SweepTree *>(elem[RIGHT]); + if (insertR) + { + return found_between; + } + else + { + return found_on_right; + } + } + } + return not_found; +} + +void +SweepTree::RemoveEvents(SweepEventQueue & queue) +{ + RemoveEvent(queue, LEFT); + RemoveEvent(queue, RIGHT); +} + +void SweepTree::RemoveEvent(SweepEventQueue &queue, Side s) +{ + if (evt[s]) { + queue.remove(evt[s]); + evt[s] = nullptr; + } +} + +int +SweepTree::Remove(SweepTreeList &list, SweepEventQueue &queue, + bool rebalance) +{ + RemoveEvents(queue); + AVLTree *tempR = static_cast<AVLTree *>(list.racine); + int err = AVLTree::Remove(tempR, rebalance); + list.racine = static_cast<SweepTree *>(tempR); + MakeDelete(); + if (list.nbTree <= 1) + { + list.nbTree = 0; + list.racine = nullptr; + } + else + { + if (list.racine == list.trees + (list.nbTree - 1)) + list.racine = this; + list.trees[--list.nbTree].Relocate(this); + } + return err; +} + +int +SweepTree::Insert(SweepTreeList &list, SweepEventQueue &queue, + Shape *iDst, int iAtPoint, bool rebalance, bool sweepSens) +{ + // if the root node doesn't exist, make this one the root node + if (list.racine == nullptr) + { + list.racine = this; + return avl_no_err; + } + SweepTree *insertL = nullptr; + SweepTree *insertR = nullptr; + // use the Find call to figure out the exact position where this needs to go + int insertion = + list.racine->Find(iDst->getPoint(iAtPoint).x, this, + insertL, insertR, sweepSens); + + // if the insertion type is found_exact or found_between this new node is getting in between + // two existing nodes, which demands that any intersection event that was recorded between + // the two must be destroyed now *cuz they are no longer together* :-( + if (insertion == found_exact) { // not sure if these if statements are really needed. + if (insertR) { + insertR->RemoveEvent(queue, LEFT); + } + if (insertL) { + insertL->RemoveEvent(queue, RIGHT); + } + + } else if (insertion == found_between) { + insertR->RemoveEvent(queue, LEFT); + insertL->RemoveEvent(queue, RIGHT); + } + // let the parent class do the adding now + AVLTree *tempR = static_cast<AVLTree *>(list.racine); + int err = + AVLTree::Insert(tempR, insertion, static_cast<AVLTree *>(insertL), + static_cast<AVLTree *>(insertR), rebalance); + list.racine = static_cast<SweepTree *>(tempR); + return err; +} + +// insertAt() is a speedup on the regular sweepline: if the polygon contains a point of high degree, you +// get a set of edge that are to be added in the same position. thus you insert one edge with a regular insert(), +// and then insert all the other in a doubly-linked list fashion. this avoids the Find() call, but is O(d^2) worst-case +// where d is the number of edge to add in this fashion. hopefully d remains small + +int +SweepTree::InsertAt(SweepTreeList &list, SweepEventQueue &queue, + Shape */*iDst*/, SweepTree *insNode, int fromPt, + bool rebalance, bool sweepSens) +{ + // if root node not set, set it + if (list.racine == nullptr) + { + list.racine = this; + return avl_no_err; + } + + // the common point between edges + Geom::Point fromP; + fromP = src->pData[fromPt].rx; + // get the edge vector + Geom::Point nNorm; + nNorm = src->getEdge(bord).dx; + // make sure the edge vector is top to bottom or if horizontal + if (src->getEdge(bord).st > src->getEdge(bord).en) + { + nNorm = -nNorm; + } + if (sweepSens == false) // why tho? + { + nNorm = -nNorm; + } + + Geom::Point bNorm; // the existing edge (kinda the reference node u can say) that we wanna add this one near to + bNorm = insNode->src->getEdge(insNode->bord).dx; + if (insNode->src->getEdge(insNode->bord).st > + insNode->src->getEdge(insNode->bord).en) + { + bNorm = -bNorm; + } + + SweepTree *insertL = nullptr; + SweepTree *insertR = nullptr; + double ang = cross(bNorm, nNorm); // you can use the diagram in the header documentation to make sense of cross product's direction. + if (ang == 0) // node on top of this one, so we just add right here + { + insertL = insNode; + insertR = static_cast<SweepTree *>(insNode->elem[RIGHT]); + } + else if (ang > 0) // edge is to the left + { + // initialize such that we are adding this edge between insNode (reference) and whatever is + // to it's right, this position will change as we go left now + insertL = insNode; + insertR = static_cast<SweepTree *>(insNode->elem[RIGHT]); + + // start moving to the left + while (insertL) + { + if (insertL->src == src) + { + if (insertL->src->getEdge(insertL->bord).st != fromPt + && insertL->src->getEdge(insertL->bord).en != fromPt) + { + break; // if the edge on the left has no endpoint that's fromPt, means we have gone too far, so break + // we only case about inserting this at the right position relative to the + // existing edges that are connected to fromPt + } + } + else + { // TODO: has to do with case when an edge can come from another shape + int ils = insertL->src->getEdge(insertL->bord).st; + int ile = insertL->src->getEdge(insertL->bord).en; + if ((insertL->src->pData[ils].rx[0] != fromP[0] + || insertL->src->pData[ils].rx[1] != fromP[1]) + && (insertL->src->pData[ile].rx[0] != fromP[0] + || insertL->src->pData[ile].rx[1] != fromP[1])) + { + break; + } + } + // bNorm is the new edge (the new reference to which we will compare the new edge (to + // add)) + bNorm = insertL->src->getEdge(insertL->bord).dx; + if (insertL->src->getEdge(insertL->bord).st > + insertL->src->getEdge(insertL->bord).en) + { + bNorm = -bNorm; + } + ang = cross(bNorm, nNorm); + if (ang <= 0) // the new edge should go to the right of this one, so break as insertL and insertR are perfect as they are + { + break; + } + insertR = insertL; // otherwise go left + insertL = static_cast<SweepTree *>(insertR->elem[LEFT]); + } + } + else if (ang < 0) // the new edge goes to the right + { + // initialize such that we are adding this edge between insNode (reference) and whatever is + // to it's right, this position will change as we go left now + insertL = insNode; + insertR = static_cast<SweepTree *>(insNode->elem[RIGHT]); + + // start moving to the right now + while (insertR) + { + if (insertR->src == src) + { + if (insertR->src->getEdge(insertR->bord).st != fromPt + && insertR->src->getEdge(insertR->bord).en != fromPt) + { + break; // is the right edge not really attached to fromPt at all? so break + } + } + else + { + int ils = insertR->src->getEdge(insertR->bord).st; + int ile = insertR->src->getEdge(insertR->bord).en; + if ((insertR->src->pData[ils].rx[0] != fromP[0] + || insertR->src->pData[ils].rx[1] != fromP[1]) + && (insertR->src->pData[ile].rx[0] != fromP[0] + || insertR->src->pData[ile].rx[1] != fromP[1])) + { + break; + } + } + // the new reference vector that we wanna compare to + bNorm = insertR->src->getEdge(insertR->bord).dx; + if (insertR->src->getEdge(insertR->bord).st > + insertR->src->getEdge(insertR->bord).en) + { + bNorm = -bNorm; + } + ang = cross(bNorm, nNorm); + if (ang > 0) // oh the edge goes to the left? so we just break since insertL and insertR are perfect then + { + break; + } + insertL = insertR; // go further to the right + insertR = static_cast<SweepTree *>(insertL->elem[RIGHT]); + } + } + + int insertion = found_between; // by default set to found_between + + if (insertL == nullptr) { // if nothing to left, it's found_on_left + insertion = found_on_left; + } + if (insertR == nullptr) { // if nothing on right, it's found_on_right + insertion = found_on_right; + } + + if (insertion == found_exact) { + /* FIXME: surely this can never be called? */ // yea never called it looks like :P + if (insertR) { + insertR->RemoveEvent(queue, LEFT); + } + if (insertL) { + insertL->RemoveEvent(queue, RIGHT); + } + } else if (insertion == found_between) { // if found_between we do clear any events associated to the two nodes who are now no longer gonna be adjacent + insertR->RemoveEvent(queue, LEFT); + insertL->RemoveEvent(queue, RIGHT); + } + + // let the parent do the actual insertion stuff in the tree now + AVLTree *tempR = static_cast<AVLTree *>(list.racine); + int err = + AVLTree::Insert(tempR, insertion, static_cast<AVLTree *>(insertL), + static_cast<AVLTree *>(insertR), rebalance); + list.racine = static_cast<SweepTree *>(tempR); + return err; +} + +void +SweepTree::Relocate(SweepTree * to) +{ + if (this == to) + return; + AVLTree::Relocate(to); + to->src = src; + to->bord = bord; + to->sens = sens; + to->evt[LEFT] = evt[LEFT]; + to->evt[RIGHT] = evt[RIGHT]; + to->startPoint = startPoint; + if (unsigned(bord) < src->swsData.size()) + src->swsData[bord].misc = to; + if (unsigned(bord) < src->swrData.size()) + src->swrData[bord].misc = to; + if (evt[LEFT]) + evt[LEFT]->sweep[RIGHT] = to; + if (evt[RIGHT]) + evt[RIGHT]->sweep[LEFT] = to; +} + +// TODO check if ignoring these parameters is bad +void +SweepTree::SwapWithRight(SweepTreeList &/*list*/, SweepEventQueue &/*queue*/) +{ + SweepTree *tL = this; + SweepTree *tR = static_cast<SweepTree *>(elem[RIGHT]); + + tL->src->swsData[tL->bord].misc = tR; + tR->src->swsData[tR->bord].misc = tL; + + { + Shape *swap = tL->src; + tL->src = tR->src; + tR->src = swap; + } + { + int swap = tL->bord; + tL->bord = tR->bord; + tR->bord = swap; + } + { + int swap = tL->startPoint; + tL->startPoint = tR->startPoint; + tR->startPoint = swap; + } + //{double swap=tL->invDirLength;tL->invDirLength=tR->invDirLength;tR->invDirLength=swap;} + { + bool swap = tL->sens; + tL->sens = tR->sens; + tR->sens = swap; + } +} + +void +SweepTree::Avance(Shape */*dstPts*/, int /*curPoint*/, Shape */*a*/, Shape */*b*/) +{ + return; +/* if ( curPoint != startPoint ) { + int nb=-1; + if ( sens ) { +// nb=dstPts->AddEdge(startPoint,curPoint); + } else { +// nb=dstPts->AddEdge(curPoint,startPoint); + } + if ( nb >= 0 ) { + dstPts->swsData[nb].misc=(void*)((src==b)?1:0); + int wp=waitingPoint; + dstPts->eData[nb].firstLinkedPoint=waitingPoint; + waitingPoint=-1; + while ( wp >= 0 ) { + dstPts->pData[wp].edgeOnLeft=nb; + wp=dstPts->pData[wp].nextLinkedPoint; + } + } + startPoint=curPoint; + }*/ +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-tree.h b/src/livarot/sweep-tree.h new file mode 100644 index 0000000..c4c84a5 --- /dev/null +++ b/src/livarot/sweep-tree.h @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef INKSCAPE_LIVAROT_SWEEP_TREE_H +#define INKSCAPE_LIVAROT_SWEEP_TREE_H + +#include "livarot/AVL.h" +#include <2geom/point.h> + +class Shape; +class SweepEvent; +class SweepEventQueue; +class SweepTreeList; + + +/** + * One node in the AVL tree of edges. + * Note that these nodes will be stored in a dynamically allocated array, hence the Relocate() function. + */ + +/** + * A node in the sweep tree. For details about the sweep tree, what it is, what we do with it, + * why it's needed, check out SweepTreeList's documentation. + * + * Explanation of what is stored in evt and why: + * Say you have two edges in the sweepline `left` and `right` and an intersection is detected between + * the two. An intersection event (of type SweepEvent) is created and that event object stores + * pointer to the `left` and `right` edges (of type SweepTree). The left edge's evt[RIGHT]/evt[1] + * stores the pointer to the intersection event and the right edge's evt[LEFT]/evt[0] also stores + * it. This is done for a very important reason. If any point in time, either the LEFT or the RIGHT + * edge have to change their position in the sweepline for any reason at all (before the + * intersection point comes), we need to immediately delete that event from our list, cuz the edges + * are no longer together. + */ +class SweepTree:public AVLTree +{ +public: + SweepEvent *evt[2]; /*!< Intersection with the edge on the left and right (if any). */ + + Shape *src; /*!< Shape from which the edge comes. (When doing boolean operation on polygons, + edges can come from 2 different polygons.) */ + int bord; /*!< Edge index in the Shape. */ + bool sens; /*!< true = top->bottom; false = bottom->top. */ + int startPoint; /*!< point index in the result Shape associated with the upper end of the edge */ + + SweepTree(); + ~SweepTree() override; + + // Inits a brand new node. + + /** + * Does what usually a constructor does. + * + * @param iSrc The pointer to the shape from which this edge comes from. + * @param iBord The edge index in the shape. + * @param iWeight The weight of the edge. Used along with edge's orientation to determine + * sens. + * @param iStartPoint Point index in the *result* Shape associated with the upper end of the + * edge + */ + void MakeNew(Shape *iSrc, int iBord, int iWeight, int iStartPoint); + // changes the edge associated with this node + // goal: reuse the node when an edge follows another, which is the most common case + + /** + * Reuse this node by just changing the variables. + * + * This is useful when you have one edge ending at a point and another one starting at the + * same point. So instead of deleting one and adding another at exactly the same location, + * you can just reuse the old one and change the variables. + * + * @param iSrc The pointer to the shape from which this edge comes from. + * @param iBord The edge index in the shape. + * @param iWeight The weight of the edge. Used along with edge's orientation to determine + * sens. + * @param iStartPoint Point index in the *result* Shape associated with the upper end of the + * edge + */ + void ConvertTo(Shape *iSrc, int iBord, int iWeight, int iStartPoint); + + // Delete the contents of node. + + /** + * Delete this node. Make sure to change the pointers in any intersection event (that points to + * this node). + */ + void MakeDelete(); + + // utilites + + // the find function that was missing in the AVLTrree class + // the return values are defined in LivarotDefs.h + + /** + * Find where the new edge needs to go in the sweepline tree. + * + * Please check out the documentation of the other version of Find that takes + * a point as input, this function is exactly identical except one block of code. + * + * For that special block of code, see this picture. + * + * @image html livarot-images/find-point-same.svg + * + * The rest of the function is the same as the other Find function so you're already + * familiar with how it works. The difference is how the y == 0 case is handled. When we are + * within that block, it's established that the upper endpoint of the new edge is on our edge. + * Now we see how the rest of the edge is orientated with respect to our edge so we can decide + * where to put it. To do this, we get the edge vector of the new edge and make sure it's top + * to bottom or if horizontal left to right. Then we rotate this new edge vector + * counter-clockwise by 90 degrees and shift it so that it starts at the same point as + * bNorm.ccw() does. The picture takes two cases simultaneously, one with edge red and the + * other being edge magenta. Their normals are shown with dotted lines and the shifted versions + * are lighter in color. + * + * Now if sweepSens is false, we take a cross product of new edge's normal with this edge's + * normal or cross(nNorm, bNorm). To figure out the direction of a cross product in SVG + * coordinates use this variation of right hand rule. Let index finger point to vector A. Let + * middle finger point to vector B. If thumb points out of page, cross product is negative, if + * it points inside the page, cross product is positive. Now you can see how when sweepSens is + * false, the cross product checks out with the orientation of the edges. If the cross product + * turns out to be zero, then we take dot product and let that decide. TODO: Which orientation + * will do what though? + * + * What about the other condition of sweepSens? When would sweepSens be true and how would that + * be useful. I think it has to do with sweeping in the opposite direction as a comment in the + * function already says. + * + * @image html livarot-images/find-point-same-opp-sweepsense.svg + * + * In this image, you can see the edges go bottom to top and then, you'd need cross(bNorm, + * nNorm) to figure out the correct orientation. + * + * @param iPt The point whose location we need to find in the sweepline. + * @param newOne The new edge that we wanna insert. To which point iPt belongs. + * @param insertL The edge that should go on the left (looking from the position where the new + * edge should go). This is set by the function. + * @param insertR The edge that should go on the right (looking form the position where the new + * edge should go). This is set by the function. + * @param sweepSens TODO: Why is this set to true? When it should be false when coming from + * ConvertToShape? + */ + int Find(Geom::Point const &iPt, SweepTree *newOne, SweepTree *&insertL, + SweepTree *&insertR, bool sweepSens = true); + + /** + * Find the place for a point (not an edge) in the sweepline tree. + * + * @image html livarot-images/find-point.svg + * + * To learn the algorithm, check the comments in the function body while referring back to + * the picture shown above. A brief summary follows. + * + * We start by taking our edge vector and if it goes bottom to top, or is horizontal and goes + * right to left, we flip its direction. In the picture bNorm shows this edge vector after any + * flipping. We rotate bNorm by 90 degrees counter-clockwise to get the normal vector. + * Then we take the start point of the edge vector (the original start point not the + * one after flipping) and draw a vector from the start point to the point whose position we + * are trying to find (iPt), we call this the diff vector. In the picture I have drawn these + * for three points red, blue and green. Now we take the dot product of this diff with the + * normal vectors. As you would now, a dot product has the formula: + * \f[ \vec{A}\cdot\vec{B} = |\vec{A}||\vec{B}|\cos\theta \f] + * \f$ \theta \f$ here is the angle between the two. As you would know, \f$ \cos \f$ is + * positive as long as the angle is between +90 and -90. At 90 degrees it becomes zero and + * greater than 90 or smaller than -90 it becomes negative. Thus the sign of the dot product + * can be used as an indicator of the angle between the normal vector and the diff vector. + * If this angle is within 90 to -90, it means the point lies to the right of the original + * edge. If it lies on 90 or -90, it means the point lies on the same line as the edge and + * if it's greater than 90 or smaller than -90, the point lies on the left side of the + * original edge. + * + * One thing to note, the blue point here is kinda wrong. You can't have another edge starting + * above the already existing edge when sweeping done (that's just not possible). So sorry + * about that. But the math checks out anyways. TODO: Maybe fix this to avoid confusion? + * + * One important point to see here is that the edge vector will be flipped however it's start + * point remains the same as the original one, this is not a problem as you can see in the + * image below. I chose the other point as the start point and everything works out the same. + * + * @image html livarot-images/find-point-2.svg + * + * I changed the starting point and redrew the diff vectors. Then I projected them such that + * their origin is the same as that of the normal. Measuring the angles again, everything + * remains the same. + * + * There is one more confusion part you'd find in this function. The part where left and right + * child are checked and you'd see child as well as elem pointers being used. + * + * @image html livarot-images/sweep-tree.svg + * + * The picture above shows you a how the sweepline tree structure looks like. I've used numbers + * instead of actual edges just for the purpose of illustration. The structure is an AVL tree + * as well as double-linked list. The nodes are arranged in a tree structure that balances + * itself but each node has two more points elem[LEFT] and elem[RIGHT] that can be used to + * navigate the linked list. In reality, the linked list structure is what's needed, but having + * an AVL tree makes searching really easy. + * + * See the comments in the if blocks in the function body. I've given examples from this + * diagram to explain stuff. + * + * @param iPt The point whose position we are trying to find. + * @param insertL The edge on the left (from the location where this point is supposed to go) + * @param insertR The edge on the right (from the location where this point is supposed to go) + * + * @return The found_* codes from LivarotDefs.h. See the file to learn about them. + */ + int Find(Geom::Point const &iPt, SweepTree *&insertL, SweepTree *&insertR); + + /// Remove sweepevents attached to this node. + + /** + * Remove any events attached to this node. + * + * Since the event the other node referring to this event will also have it's + * evt value cleared. + * + * @param queue Reference to the event queue. + */ + void RemoveEvents(SweepEventQueue &queue); + + /** + * Remove event on the side s if it exists from event queue. + * + * Since the event the other node referring to this event will also have it's + * evt value cleared. + * + * @param queue Reference to the event queue. + * @param s The side to remove the event from. + */ + void RemoveEvent(SweepEventQueue &queue, Side s); + + // overrides of the AVLTree functions, to account for the sorting in the tree + // and some other stuff + int Remove(SweepTreeList &list, SweepEventQueue &queue, bool rebalance = true); + + /** + * Insert this node at it's appropriate position in the sweepline tree. + * + * The function works by calling the Find function to let it find the appropriate + * position where this node should go, which it does by traversing the whole search + * tree. + * + * @param list A reference to the sweepline tree. + * @param queue A reference to the event queue. + * @param iDst Pointer to the shape to which this edge belongs to. + * @param iAtPoint The point at which we are adding this edge. + * @param rebalance TODO: Confirm this but most likely has to do with whether AVL should + * rebalance or not. + * @param sweepSens TODO: The same variable as in Find, has to do with sweepline direction. + */ + int Insert(SweepTreeList &list, SweepEventQueue &queue, Shape *iDst, + int iAtPoint, bool rebalance = true, bool sweepSens = true); + + /** + * Insert this node near an existing node. + * + * This is a simplification to the other Insert function. The normal Insert function would + * traverse the whole tree to find an appropriate place to add the current node. There are + * situations where we just added an edge and we have more edges connected to the same point + * that we wanna add. This function can be used to directly traverse left and right around + * the existing node to see where this edge would fit. Saves us full Find call. This searching + * is exactly how you'd insert something in a doubly-linked list. The function uses cross + * products to see where this edge should go relative to the existing one and then has loops + * to find the right spot for it. + * + * @image html livarot-images/find-point.svg + * + * I'll use this image to explain some stuff in the code body. + * + * @param list A reference to the sweepline tree. + * @param queue A reference to the event queue. + * @param iDst Pointer to the shape to which this edge belongs to. + * @param insNode Pointer to the node near which this is to be added. + * @param fromPt The point at which we are adding this edge. + * @param rebalance TODO: Confirm this but most likely has to do with whether AVL should + * rebalance or not. + * @param sweepSens TODO: The same variable as in Find, has to do with sweepline direction. + */ + int InsertAt(SweepTreeList &list, SweepEventQueue &queue, Shape *iDst, + SweepTree *insNode, int fromPt, bool rebalance = true, bool sweepSens = true); + + /// Swap nodes, or more exactly, swap the edges in them. + + /** + * Used to swap two nodes with each other. Basically the data in the nodes are swapped + * not their addresses or locations in memory. + * + * Hence anyone referencing these nodes will get invalid (or unexpected) references since + * the data got swapped out. Therefore you must clear any events that might have references to + * these nodes. + * + * @param list Reference to the sweepline tree. Useless parameter. + * @param queue Reference to the event queue. Useless parameter. + */ + void SwapWithRight(SweepTreeList &list, SweepEventQueue &queue); + + /** + * Useless function. No active code in the function body. I suspected this became + * useless after Shape::Avance was implemented. + */ + void Avance(Shape *dst, int nPt, Shape *a, Shape *b); + + /** + * TODO: Probably has to do with some AVL relocation. Only called once from Node removal code. + */ + void Relocate(SweepTree *to); +}; + + +#endif /* !INKSCAPE_LIVAROT_SWEEP_TREE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |