428 lines
14 KiB
C++
428 lines
14 KiB
C++
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// 2d geometry classes - implements 2d kurve offset for use in dll
|
|
//
|
|
// g.j.hawkesford August 2003
|
|
//
|
|
// This program is released under the BSD license. See the file COPYING for details.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#include "geometry.h"
|
|
using namespace geoff_geometry;
|
|
|
|
namespace geoff_geometry
|
|
{
|
|
static Kurve eliminateLoops(const Kurve& k, const Kurve& originalk, double offset, int& ret);
|
|
static bool DoesIntersInterfere(const Point& pInt, const Kurve& k, double offset);
|
|
|
|
int Kurve::Offset(vector<Kurve*>& OffsetKurves, double offset, int direction, int method, int& ret) const
|
|
{
|
|
|
|
switch (method) {
|
|
case NO_ELIMINATION:
|
|
case BASIC_OFFSET: {
|
|
Kurve* ko = new Kurve;
|
|
int n = OffsetMethod1(*ko, offset, direction, method, ret);
|
|
OffsetKurves.push_back(ko);
|
|
return n;
|
|
}
|
|
|
|
default:
|
|
FAILURE(L"Requested Offsetting Method not available");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int Kurve::OffsetMethod1(Kurve& kOffset, double off, int direction, int method, int& ret) const
|
|
{
|
|
// offset kurve with simple span elimination
|
|
// direction 1 = left, -1 = right
|
|
|
|
// ret = 0 - kurve offset ok
|
|
// = 1 - kurve has differential scale (not allowed)
|
|
// = 2 - offset failed
|
|
// = 3 - offset too large
|
|
if (this == &kOffset) {
|
|
FAILURE(L"Illegal Call - 'this' must not be kOffset");
|
|
}
|
|
double offset = (direction == GEOFF_LEFT) ? off : -off;
|
|
|
|
if (fabs(offset) < geoff_geometry::TOLERANCE || m_nVertices < 2) {
|
|
kOffset = *this;
|
|
ret = 0;
|
|
return 1;
|
|
}
|
|
|
|
Span curSpan, curSpanOff; // current & offset spans
|
|
Span prevSpanOff; // previous offset span
|
|
Point p0, p1; // Offset span intersections
|
|
|
|
// offset Kurve
|
|
kOffset = Matrix(*this);
|
|
|
|
if (m_mirrored) {
|
|
offset = -offset;
|
|
}
|
|
int RollDir = (off < 0) ? direction : -direction; // Roll arc direction
|
|
|
|
double scalex;
|
|
if (!GetScale(scalex)) {
|
|
ret = 1;
|
|
return 0; // differential scale
|
|
}
|
|
offset /= scalex;
|
|
|
|
bool bClosed = Closed();
|
|
int nspans = nSpans();
|
|
if (bClosed) {
|
|
Get(nspans, curSpan, true); // assign previous span for closed
|
|
|
|
prevSpanOff = curSpan.Offset(offset);
|
|
nspans++; // read first again
|
|
}
|
|
|
|
for (int spannumber = 1; spannumber <= nspans; spannumber++) {
|
|
if (spannumber > nSpans()) {
|
|
Get(1, curSpan, true); // closed kurve - read first span again
|
|
}
|
|
else {
|
|
Get(spannumber, curSpan, true);
|
|
}
|
|
|
|
if (!curSpan.NullSpan) {
|
|
int numint = 0;
|
|
curSpanOff = curSpan.Offset(offset);
|
|
curSpanOff.ID = 0;
|
|
if (!kOffset.m_started) {
|
|
kOffset.Start(curSpanOff.p0);
|
|
kOffset.AddSpanID(0);
|
|
}
|
|
|
|
if (spannumber > 1) {
|
|
// see if tangent
|
|
double d = curSpanOff.p0.Dist(prevSpanOff.p1);
|
|
if ((d > geoff_geometry::TOLERANCE)
|
|
&& (!curSpanOff.NullSpan && !prevSpanOff.NullSpan)) {
|
|
// see if offset spans intersect
|
|
|
|
double cp = prevSpanOff.ve ^ curSpanOff.vs;
|
|
bool inters = (cp > 0 && direction == GEOFF_LEFT)
|
|
|| (cp < 0 && direction == GEOFF_RIGHT);
|
|
|
|
if (inters) {
|
|
double t[4];
|
|
numint = prevSpanOff.Intof(curSpanOff, p0, p1, t);
|
|
}
|
|
|
|
if (numint == 1) {
|
|
// intersection - modify previous endpoint
|
|
kOffset.Replace(
|
|
kOffset.m_nVertices - 1,
|
|
prevSpanOff.dir,
|
|
p0,
|
|
prevSpanOff.pc,
|
|
prevSpanOff.ID
|
|
);
|
|
}
|
|
else {
|
|
// 0 or 2 intersections, add roll around (remove -ve loops in elimination
|
|
// function)
|
|
if (kOffset.Add(RollDir, curSpanOff.p0, curSpan.p0, false)) {
|
|
kOffset.AddSpanID(ROLL_AROUND);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add span
|
|
if (spannumber < m_nVertices) {
|
|
curSpanOff.ID = spannumber;
|
|
kOffset.Add(curSpanOff, false);
|
|
}
|
|
else if (numint == 1) { // or replace the closed first span
|
|
kOffset.Replace(0, 0, p0, Point(0, 0), 0);
|
|
}
|
|
}
|
|
if (!curSpanOff.NullSpan) {
|
|
prevSpanOff = curSpanOff;
|
|
}
|
|
} // end of main pre-offsetting loop
|
|
|
|
|
|
#ifdef _DEBUG
|
|
// testDraw->AddKurve("", &kOffset, 0, GREEN);
|
|
// outXML oxml(L"c:\\temp\\eliminateLoops.xml");
|
|
// oxml.startElement(L"eliminateLoops");
|
|
// oxml.Write(kOffset, L"kOffset");
|
|
// oxml.endElement();
|
|
#endif
|
|
// eliminate loops
|
|
if (method == NO_ELIMINATION) {
|
|
ret = 0;
|
|
return 1;
|
|
}
|
|
kOffset = eliminateLoops(kOffset, *this, offset, ret);
|
|
|
|
if (ret == 0 && bClosed) {
|
|
// check for inverted offsets of closed kurves
|
|
if (kOffset.Closed()) {
|
|
double a = Area();
|
|
int dir = (a < 0);
|
|
double ao = kOffset.Area();
|
|
int dirOffset = ao < 0;
|
|
|
|
if (dir != dirOffset) {
|
|
ret = 3;
|
|
}
|
|
else {
|
|
// check area change compatible with offset direction - catastrophic failure
|
|
bool bigger = (a > 0 && offset > 0) || (a < 0 && offset < 0);
|
|
if (bigger && fabs(ao) < fabs(a)) {
|
|
ret = 2;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
ret = 2; // started closed but now open??
|
|
}
|
|
}
|
|
return (ret == 0) ? 1 : 0;
|
|
}
|
|
|
|
|
|
static Kurve eliminateLoops(const Kurve& k, const Kurve& originalk, double offset, int& ret)
|
|
{
|
|
// a simple loop elimination routine based on first offset ideas in Peps
|
|
// this needs extensive work for future
|
|
// start point mustn't disappear & only one valid offset is determined
|
|
//
|
|
// ret = 0 for ok
|
|
// ret = 2 for impossible geometry
|
|
|
|
Span sp0, sp1;
|
|
Point pInt, pIntOther;
|
|
|
|
Kurve ko; // eliminated output
|
|
ko = Matrix(k);
|
|
int kinVertex = 0;
|
|
|
|
while (kinVertex <= k.nSpans()) {
|
|
bool clipped = false; // not in a clipped section (assumption with this simple method)
|
|
|
|
sp0.dir = k.Get(kinVertex, sp0.p0, sp0.pc);
|
|
sp0.ID = k.GetSpanID(kinVertex++);
|
|
if (kinVertex == 1) {
|
|
ko.Start(sp0.p0); // start point mustn't disappear for this simple method
|
|
ko.AddSpanID(sp0.ID);
|
|
}
|
|
if (kinVertex <= k.nSpans()) { // any more?
|
|
int ksaveVertex = kinVertex;
|
|
sp0.dir = k.Get(kinVertex, sp0.p1, sp0.pc); // first span
|
|
sp0.ID = k.GetSpanID(kinVertex++);
|
|
|
|
sp0.SetProperties(true);
|
|
|
|
int ksaveVertex1 = kinVertex; // mark position AA
|
|
if (kinVertex <= k.nSpans()) { // get the next but one span
|
|
sp1.dir = k.Get(kinVertex, sp1.p0, sp1.pc);
|
|
sp1.ID = k.GetSpanID(kinVertex++);
|
|
int ksaveVertex2 = kinVertex; // mark position BB
|
|
|
|
int fwdCount = 0;
|
|
while (kinVertex <= k.nSpans()) {
|
|
sp1.dir = k.Get(kinVertex, sp1.p1, sp1.pc); // check span
|
|
sp1.ID = k.GetSpanID(kinVertex++);
|
|
sp1.SetProperties(true);
|
|
|
|
double t[4];
|
|
int numint = sp0.Intof(sp1, pInt, pIntOther, t); // find span intersections
|
|
if (numint && sp0.p0.Dist(pInt) < geoff_geometry::TOLERANCE) {
|
|
numint = 0; // check that intersection is not at the start of the check span
|
|
}
|
|
if (numint) {
|
|
|
|
if (numint == 2) {
|
|
// choose first intercept on sp0
|
|
Span spd = sp0;
|
|
spd.p1 = pInt;
|
|
spd.SetProperties(true);
|
|
double dd = spd.length;
|
|
|
|
spd.p1 = pIntOther;
|
|
spd.SetProperties(true);
|
|
if (dd > spd.length) {
|
|
pInt = pIntOther;
|
|
}
|
|
numint = 1;
|
|
}
|
|
ksaveVertex = ksaveVertex1;
|
|
|
|
clipped = true; // in a clipped section
|
|
if (!DoesIntersInterfere(pInt, originalk, offset)) {
|
|
sp0.p1 = pInt; // ok so truncate this span to the intersection
|
|
clipped = false; // end of clipped section
|
|
break;
|
|
}
|
|
// no valid intersection found so carry on
|
|
}
|
|
sp1.p0 = sp1.p1; // next
|
|
ksaveVertex1 = ksaveVertex2; // pos AA = BB
|
|
ksaveVertex2 = kinVertex; // mark
|
|
|
|
if ((kinVertex > k.nSpans() || fwdCount++ > 25) && !clipped) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clipped) {
|
|
ret = 2; // still in a clipped section - error
|
|
|
|
return ko;
|
|
}
|
|
|
|
ko.Add(sp0, false);
|
|
|
|
kinVertex = ksaveVertex;
|
|
}
|
|
}
|
|
ret = 0;
|
|
|
|
return ko; // no more spans - seems ok
|
|
}
|
|
|
|
|
|
static bool DoesIntersInterfere(const Point& pInt, const Kurve& k, double offset)
|
|
{
|
|
// check that intersections don't interfere with the original kurve
|
|
Span sp;
|
|
Point dummy;
|
|
int kCheckVertex = 0;
|
|
k.Get(kCheckVertex++, sp.p0, sp.pc);
|
|
|
|
offset = fabs(offset) - geoff_geometry::TOLERANCE;
|
|
while (kCheckVertex <= k.nSpans()) {
|
|
sp.dir = k.Get(kCheckVertex++, sp.p1, sp.pc);
|
|
sp.SetProperties(true);
|
|
// check for interference
|
|
if (Dist(sp, pInt, dummy) < offset) {
|
|
return true;
|
|
}
|
|
sp.p0 = sp.p1;
|
|
}
|
|
return false; // intersection is ok
|
|
}
|
|
} // namespace geoff_geometry
|
|
|
|
|
|
static struct iso
|
|
{
|
|
Span sp;
|
|
Span off;
|
|
} isodata;
|
|
static void isoRadius(Span& before, Span& blend, Span& after, double radius);
|
|
|
|
int Kurve::OffsetISOMethod(Kurve& kOut, double off, int direction, bool BlendAll) const
|
|
{
|
|
// produces a special offset Kurve - observing so-called ISO radii
|
|
// eg line/arc/line tangent - keep arc radius constant
|
|
// this method also considers arc/arc/arc etc.
|
|
// interior radius must be smallest of triplet for above.
|
|
|
|
// parameters:-
|
|
// Output kOut resulting kurve
|
|
// Input off offset amount
|
|
// Input direction offset direction (LEFT or RIGHT)
|
|
// Input BlendAall if false only consider ISO radius for LINE/ARC/LINE
|
|
// if true consider all blended radii (ARC/ARC/ARC etc.)
|
|
double offset = (direction == GEOFF_LEFT) ? off : -off;
|
|
if (FEQZ(off) || nSpans() < 1) {
|
|
kOut = *this;
|
|
return 1;
|
|
}
|
|
double cptol = 1.0e-05;
|
|
std::vector<iso> spans;
|
|
for (int i = 0; i < nSpans(); i++) { // store all spans and offsets
|
|
Get(i + 1, isodata.sp, true, true);
|
|
isodata.off = isodata.sp.Offset(offset);
|
|
spans.push_back(isodata);
|
|
}
|
|
|
|
for (int i = 0; i < nSpans() - 1; i++) { // calculate intersections for none tangent spans
|
|
if (fabs(spans[i].off.ve ^ spans[i + 1].off.vs) > cptol) {
|
|
spans[i].off.JoinSeparateSpans(spans[i + 1].off);
|
|
}
|
|
}
|
|
|
|
for (int i = 1; i < nSpans() - 1; i++) { // deal with isoradii
|
|
if (spans[i].off.dir) {
|
|
if (BlendAll) { // interior radius should be smaller than neighbours
|
|
if (spans[i - 1].sp.dir) {
|
|
if (spans[i - 1].sp.radius < spans[i].sp.radius) {
|
|
continue;
|
|
}
|
|
}
|
|
if (spans[i + 1].sp.dir) {
|
|
if (spans[i + 1].sp.radius < spans[i].sp.radius) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if ((spans[i - 1].off.dir || spans[i + 1].off.dir)) {
|
|
continue; // linear neighbours only
|
|
}
|
|
}
|
|
|
|
if ((fabs(spans[i - 1].sp.ve ^ spans[i].sp.vs) < cptol)
|
|
&& (fabs(spans[i].sp.ve ^ spans[i + 1].sp.vs) < cptol)) {
|
|
// isoradius - calculate the new offset radius and modify neighbouring spans
|
|
isoRadius(spans[i - 1].off, spans[i].off, spans[i + 1].off, spans[i].sp.radius);
|
|
}
|
|
}
|
|
}
|
|
|
|
kOut.Start(spans[0].off.p0); // start point
|
|
for (int i = 0; i < nSpans(); i++) {
|
|
kOut.Add(spans[i].off.dir, spans[i].off.p1, spans[i].off.pc); // output all spans
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void isoRadius(Span& before, Span& blend, Span& after, double radius)
|
|
{
|
|
// calculate the new offset radius and modify neighbouring spans
|
|
int direction = ((before.ve ^ after.vs) > 0) ? 1 : -1; // offset direction
|
|
Span beforeOff = before.Offset(direction * radius);
|
|
Span afterOff = after.Offset(direction * radius);
|
|
int turnLeft = ((before.ve ^ after.vs) > 0) ? 1 : -1;
|
|
if (before.dir == LINEAR) {
|
|
CLine b(beforeOff);
|
|
if (after.dir == LINEAR) {
|
|
CLine a(afterOff);
|
|
blend.pc = b.Intof(a);
|
|
}
|
|
else {
|
|
Circle a(afterOff);
|
|
b.Intof(turnLeft * after.dir, a, blend.pc);
|
|
}
|
|
}
|
|
else {
|
|
Circle b(beforeOff);
|
|
|
|
if (after.dir == LINEAR) {
|
|
CLine a(afterOff);
|
|
a.Intof(-turnLeft * before.dir, b, blend.pc);
|
|
}
|
|
else {
|
|
// arc arc
|
|
Circle a(afterOff);
|
|
int leftright = ((Vector2d(b.pc, blend.pc) ^ Vector2d(b.pc, a.pc)) < 0) ? 1 : -1;
|
|
b.Intof(leftright, a, blend.pc);
|
|
}
|
|
}
|
|
before.p1 = blend.p0 = before.Near(blend.pc);
|
|
after.p0 = blend.p1 = after.Near(blend.pc);
|
|
}
|