// 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& 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 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); }