//------------------------------------------------------------------------------ internal int PolySort(OutRec or1, OutRec or2) { if (or1 == or2) return 0; else if (or1.pts == null || or2.pts == null) { if ((or1.pts == null) != (or2.pts == null)) { if (or1.pts != null) return -1; else return 1; } else return 0; } int i1, i2; if (or1.isHole) i1 = or1.FirstLeft.idx; else i1 = or1.idx; if (or2.isHole) i2 = or2.FirstLeft.idx; else i2 = or2.idx; int result = i1 - i2; if (result == 0 && (or1.isHole != or2.isHole)) { if (or1.isHole) return 1; else return -1; } return result; }
//---------------------------------------------------------------------- private void CheckHoleLinkages2(OutRec outRec1, OutRec outRec2) { //if a hole is owned by outRec2 then make it owned by outRec1 ... for (int i = 0; i < m_PolyOuts.Count; ++i) if (m_PolyOuts[i].isHole && m_PolyOuts[i].bottomPt != null && m_PolyOuts[i].FirstLeft == outRec2) m_PolyOuts[i].FirstLeft = outRec1; }
//------------------------------------------------------------------------------ private bool Orientation(OutRec outRec, bool UseFull64BitRange) { OutPt opBottom = outRec.pts, op = outRec.pts.next; while (op != outRec.pts) { if (op.pt.Y >= opBottom.pt.Y) { if (op.pt.Y > opBottom.pt.Y || op.pt.X < opBottom.pt.X) opBottom = op; } op = op.next; } IntPoint vec1 = new IntPoint(op.pt.X - op.prev.pt.X, op.pt.Y - op.prev.pt.Y); IntPoint vec2 = new IntPoint(op.next.pt.X - op.pt.X, op.next.pt.Y - op.pt.Y); if (UseFull64BitRange) { Int128 cross = Int128.Int128Mul(vec1.X, vec2.Y) - Int128.Int128Mul(vec2.X, vec1.Y); return !cross.IsNegative(); } else { return (vec1.X * vec2.Y - vec2.X * vec1.Y) > 0; } }
//------------------------------------------------------------------------------ internal OutRec FindAppendLinkEnd(OutRec outRec) { while (outRec.AppendLink != null) outRec = outRec.AppendLink; return outRec; }
//---------------------------------------------------------------------- private void FixupFirstLefts2(OutRec OldOutRec, OutRec NewOutRec) { foreach (OutRec outRec in m_PolyOuts) if (outRec.FirstLeft == OldOutRec) outRec.FirstLeft = NewOutRec; }
//------------------------------------------------------------------------------ private void FixupOutPolygon(OutRec outRec) { //FixupOutPolygon() - removes duplicate points and simplifies consecutive //parallel edges by removing the middle vertex. OutPt lastOK = null; outRec.pts = outRec.bottomPt; OutPt pp = outRec.bottomPt; for (;;) { if (pp.prev == pp || pp.prev == pp.next) { DisposeOutPts(pp); outRec.pts = null; outRec.bottomPt = null; return; } //test for duplicate points and for same slope (cross-product) ... if (PointsEqual(pp.pt, pp.next.pt) || SlopesEqual(pp.prev.pt, pp.pt, pp.next.pt, m_UseFullRange)) { lastOK = null; OutPt tmp = pp; if (pp == outRec.bottomPt) { if (tmp.prev.pt.Y > tmp.next.pt.Y) outRec.bottomPt = tmp.prev; else outRec.bottomPt = tmp.next; outRec.pts = outRec.bottomPt; outRec.bottomPt.idx = outRec.idx; } pp.prev.next = pp.next; pp.next.prev = pp.prev; pp = pp.prev; tmp = null; } else if (pp == lastOK) break; else { if (lastOK == null) lastOK = pp; pp = pp.next; } } }
//------------------------------------------------------------------------------ private OutRec CreateOutRec() { OutRec result = new OutRec(); result.Idx = Unassigned; result.IsHole = false; result.IsOpen = false; result.FirstLeft = null; result.Pts = null; result.BottomPt = null; result.PolyNode = null; m_PolyOuts.Add(result); result.Idx = m_PolyOuts.Count - 1; return result; }
//------------------------------------------------------------------------------ bool Param1RightOfParam2(OutRec outRec1, OutRec outRec2) { do { outRec1 = outRec1.FirstLeft; if (outRec1 == outRec2) return true; } while (outRec1 != null); return false; }
//------------------------------------------------------------------------------ private OutRec CreateOutRec() { OutRec result = new OutRec(); result.idx = -1; result.isHole = false; result.FirstLeft = null; result.AppendLink = null; result.pts = null; result.bottomPt = null; result.bottomFlag = null; result.sides = EdgeSide.esNeither; return result; }
//------------------------------------------------------------------------------ void DisposeBottomPt(OutRec outRec) { OutPt next = outRec.bottomPt.next; OutPt prev = outRec.bottomPt.prev; if (outRec.pts == outRec.bottomPt) outRec.pts = next; outRec.bottomPt = null; next.prev = prev; prev.next = next; outRec.bottomPt = next; FixupOutPolygon(outRec); }
//---------------------------------------------------------------------- private static OutRec ParseFirstLeft(OutRec FirstLeft) { while (FirstLeft != null && FirstLeft.Pts == null) FirstLeft = FirstLeft.FirstLeft; return FirstLeft; }
//------------------------------------------------------------------------------ internal double Area(OutRec outRec) { return Area(outRec.Pts); }
//------------------------------------------------------------------------------ private void SetHoleState(TEdge e, OutRec outRec) { TEdge e2 = e.PrevInAEL; TEdge eTmp = null; while (e2 != null) { if (e2.OutIdx >= 0 && e2.WindDelta != 0) { if (eTmp == null) eTmp = e2; else if (eTmp.OutIdx == e2.OutIdx) eTmp = null; //paired } e2 = e2.PrevInAEL; } if (eTmp == null) { outRec.FirstLeft = null; outRec.IsHole = false; } else { outRec.FirstLeft = m_PolyOuts[eTmp.OutIdx]; outRec.IsHole = !outRec.FirstLeft.IsHole; } }
//------------------------------------------------------------------------------ private bool JoinPoints(Join j, OutRec outRec1, OutRec outRec2) { OutPt op1 = j.OutPt1, op1b; OutPt op2 = j.OutPt2, op2b; //There are 3 kinds of joins for output polygons ... //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same //location at the Bottom of the overlapping segment (& Join.OffPt is above). //3. StrictlySimple joins where edges touch but are not collinear and where //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. bool isHorizontal = (j.OutPt1.Pt.Y == j.OffPt.Y); if (isHorizontal && (j.OffPt == j.OutPt1.Pt) && (j.OffPt == j.OutPt2.Pt)) { //Strictly Simple join ... if (outRec1 != outRec2) return false; op1b = j.OutPt1.Next; while (op1b != op1 && (op1b.Pt == j.OffPt)) op1b = op1b.Next; bool reverse1 = (op1b.Pt.Y > j.OffPt.Y); op2b = j.OutPt2.Next; while (op2b != op2 && (op2b.Pt == j.OffPt)) op2b = op2b.Next; bool reverse2 = (op2b.Pt.Y > j.OffPt.Y); if (reverse1 == reverse2) return false; if (reverse1) { op1b = DupOutPt(op1, false); op2b = DupOutPt(op2, true); op1.Prev = op2; op2.Next = op1; op1b.Next = op2b; op2b.Prev = op1b; j.OutPt1 = op1; j.OutPt2 = op1b; return true; } else { op1b = DupOutPt(op1, true); op2b = DupOutPt(op2, false); op1.Next = op2; op2.Prev = op1; op1b.Prev = op2b; op2b.Next = op1b; j.OutPt1 = op1; j.OutPt2 = op1b; return true; } } else if (isHorizontal) { //treat horizontal joins differently to non-horizontal joins since with //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt //may be anywhere along the horizontal edge. op1b = op1; while (op1.Prev.Pt.Y == op1.Pt.Y && op1.Prev != op1b && op1.Prev != op2) op1 = op1.Prev; while (op1b.Next.Pt.Y == op1b.Pt.Y && op1b.Next != op1 && op1b.Next != op2) op1b = op1b.Next; if (op1b.Next == op1 || op1b.Next == op2) return false; //a flat 'polygon' op2b = op2; while (op2.Prev.Pt.Y == op2.Pt.Y && op2.Prev != op2b && op2.Prev != op1b) op2 = op2.Prev; while (op2b.Next.Pt.Y == op2b.Pt.Y && op2b.Next != op2 && op2b.Next != op1) op2b = op2b.Next; if (op2b.Next == op2 || op2b.Next == op1) return false; //a flat 'polygon' int Left, Right; //Op1 -. Op1b & Op2 -. Op2b are the extremites of the horizontal edges if (!GetOverlap(op1.Pt.X, op1b.Pt.X, op2.Pt.X, op2b.Pt.X, out Left, out Right)) return false; //DiscardLeftSide: when overlapping edges are joined, a spike will created //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up //on the discard Side as either may still be needed for other joins ... IntPoint Pt; bool DiscardLeftSide; if (op1.Pt.X >= Left && op1.Pt.X <= Right) { Pt = op1.Pt; DiscardLeftSide = (op1.Pt.X > op1b.Pt.X); } else if (op2.Pt.X >= Left && op2.Pt.X <= Right) { Pt = op2.Pt; DiscardLeftSide = (op2.Pt.X > op2b.Pt.X); } else if (op1b.Pt.X >= Left && op1b.Pt.X <= Right) { Pt = op1b.Pt; DiscardLeftSide = op1b.Pt.X > op1.Pt.X; } else { Pt = op2b.Pt; DiscardLeftSide = (op2b.Pt.X > op2.Pt.X); } j.OutPt1 = op1; j.OutPt2 = op2; return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); } else { //nb: For non-horizontal joins ... // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y // 2. Jr.OutPt1.Pt > Jr.OffPt.Y //make sure the polygons are correctly oriented ... op1b = op1.Next; while ((op1b.Pt == op1.Pt) && (op1b != op1)) op1b = op1b.Next; bool Reverse1 = ((op1b.Pt.Y > op1.Pt.Y) || !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt, m_UseFullRange)); if (Reverse1) { op1b = op1.Prev; while ((op1b.Pt == op1.Pt) && (op1b != op1)) op1b = op1b.Prev; if ((op1b.Pt.Y > op1.Pt.Y) || !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt, m_UseFullRange)) return false; }; op2b = op2.Next; while ((op2b.Pt == op2.Pt) && (op2b != op2)) op2b = op2b.Next; bool Reverse2 = ((op2b.Pt.Y > op2.Pt.Y) || !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt, m_UseFullRange)); if (Reverse2) { op2b = op2.Prev; while ((op2b.Pt == op2.Pt) && (op2b != op2)) op2b = op2b.Prev; if ((op2b.Pt.Y > op2.Pt.Y) || !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt, m_UseFullRange)) return false; } if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; if (Reverse1) { op1b = DupOutPt(op1, false); op2b = DupOutPt(op2, true); op1.Prev = op2; op2.Next = op1; op1b.Next = op2b; op2b.Prev = op1b; j.OutPt1 = op1; j.OutPt2 = op1b; return true; } else { op1b = DupOutPt(op1, true); op2b = DupOutPt(op2, false); op1.Next = op2; op2.Prev = op1; op1b.Prev = op2b; op2b.Next = op1b; j.OutPt1 = op1; j.OutPt2 = op1b; return true; } } }
//------------------------------------------------------------------------------ private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) { //work out which polygon fragment has the correct hole state ... if (outRec1.BottomPt == null) outRec1.BottomPt = GetBottomPt(outRec1.Pts); if (outRec2.BottomPt == null) outRec2.BottomPt = GetBottomPt(outRec2.Pts); OutPt bPt1 = outRec1.BottomPt; OutPt bPt2 = outRec2.BottomPt; if (bPt1.Pt.Y > bPt2.Pt.Y) return outRec1; else if (bPt1.Pt.Y < bPt2.Pt.Y) return outRec2; else if (bPt1.Pt.X < bPt2.Pt.X) return outRec1; else if (bPt1.Pt.X > bPt2.Pt.X) return outRec2; else if (bPt1.Next == bPt1) return outRec2; else if (bPt2.Next == bPt2) return outRec1; else if (FirstIsBottomPt(bPt1, bPt2)) return outRec1; else return outRec2; }
//------------------------------------------------------------------------------ private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) { //work out which polygon fragment has the correct hole state ... OutPt bPt1 = outRec1.bottomPt; OutPt bPt2 = outRec2.bottomPt; if (bPt1.pt.Y > bPt2.pt.Y) return outRec1; else if (bPt1.pt.Y < bPt2.pt.Y) return outRec2; else if (bPt1.pt.X < bPt2.pt.X) return outRec1; else if (bPt1.pt.X > bPt2.pt.X) return outRec2; else if (bPt1.next == bPt1) return outRec2; else if (bPt2.next == bPt2) return outRec1; else if (FirstIsBottomPt(bPt1, bPt2)) return outRec1; else return outRec2; }
//------------------------------------------------------------------------------ private void FixupOutPolygon(OutRec outRec) { //FixupOutPolygon() - removes duplicate points and simplifies consecutive //parallel edges by removing the middle vertex. OutPt lastOK = null; outRec.BottomPt = null; OutPt pp = outRec.Pts; for (;;) { if (pp.Prev == pp || pp.Prev == pp.Next) { DisposeOutPts(pp); outRec.Pts = null; return; } //test for duplicate points and collinear edges ... if ((pp.Pt == pp.Next.Pt) || (pp.Pt == pp.Prev.Pt) || (SlopesEqual(pp.Prev.Pt, pp.Pt, pp.Next.Pt, m_UseFullRange) && (!PreserveCollinear || !Pt2IsBetweenPt1AndPt3(pp.Prev.Pt, pp.Pt, pp.Next.Pt)))) { lastOK = null; OutPt tmp = pp; pp.Prev.Next = pp.Next; pp.Next.Prev = pp.Prev; pp = pp.Prev; tmp = null; } else if (pp == lastOK) break; else { if (lastOK == null) lastOK = pp; pp = pp.Next; } } outRec.Pts = pp; }
//---------------------------------------------------------------------- private void FixupFirstLefts1(OutRec OldOutRec, OutRec NewOutRec) { for (int i = 0; i < m_PolyOuts.Count; i++) { OutRec outRec = m_PolyOuts[i]; if (outRec.pts != null && outRec.FirstLeft == OldOutRec) { if (Poly2ContainsPoly1(outRec.pts, NewOutRec.pts, m_UseFullRange)) outRec.FirstLeft = NewOutRec; } } }
//------------------------------------------------------------------------------ private void UpdateOutPtIdxs(OutRec outrec) { OutPt op = outrec.Pts; do { op.Idx = outrec.Idx; op = op.Prev; } while(op != outrec.Pts); }
//------------------------------------------------------------------------------ double Area(OutRec outRec, bool UseFull64BitRange) { OutPt op = outRec.pts; if (op == null) return 0; if (UseFull64BitRange) { Int128 a = new Int128(0); do { a += Int128.Int128Mul(op.pt.X + op.prev.pt.X, op.prev.pt.Y - op.pt.Y); op = op.next; } while (op != outRec.pts); return a.ToDouble() / 2; } else { double a = 0; do { a = a + (op.pt.X + op.prev.pt.X) * (op.prev.pt.Y - op.pt.Y); op = op.next; } while (op != outRec.pts); return a/2; } }
//------------------------------------------------------------------------------ double Area(OutRec outRec) { OutPt op = outRec.Pts; if (op == null) return 0; double a = 0; do { a = a + (double)(op.Pt.X + op.Prev.Pt.X) * (double)(op.Prev.Pt.Y - op.Pt.Y); op = op.Next; } while (op != outRec.Pts); return a/2; }
//--------------------------------------------------------------------------- private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) { //work out which polygon fragment has the correct hole state ... OutPt bPt1 = outRec1.bottomPt; OutPt bPt2 = outRec2.bottomPt; if (bPt1.pt.Y > bPt2.pt.Y) return outRec1; else if (bPt1.pt.Y < bPt2.pt.Y) return outRec2; else if (bPt1.pt.X < bPt2.pt.X) return outRec1; else if (bPt1.pt.X > bPt2.pt.X) return outRec2; else if (outRec1.bottomE2 == null) return outRec2; else if (outRec2.bottomE2 == null) return outRec1; else { Int64 y1 = Math.Max(outRec1.bottomE1.ybot, outRec1.bottomE2.ybot); Int64 y2 = Math.Max(outRec2.bottomE1.ybot, outRec2.bottomE2.ybot); if (y2 == y1 || (y1 > bPt1.pt.Y && y2 > bPt1.pt.Y)) { double dx1 = Math.Max(outRec1.bottomE1.dx, outRec1.bottomE2.dx); double dx2 = Math.Max(outRec2.bottomE1.dx, outRec2.bottomE2.dx); if (dx2 > dx1) return outRec2; else return outRec1; } else if (y2 > y1) return outRec2; else return outRec1; } }
//------------------------------------------------------------------------------ private bool Orientation(OutRec outRec, bool UseFull64BitRange) { //first make sure bottomPt is correctly assigned ... OutPt opBottom = outRec.pts, op = outRec.pts.next; while (op != outRec.pts) { if (op.pt.Y >= opBottom.pt.Y) { if (op.pt.Y > opBottom.pt.Y || op.pt.X < opBottom.pt.X) opBottom = op; } op = op.next; } outRec.bottomPt = opBottom; //find vertices either side of bottomPt (skipping duplicate points) .... OutPt opPrev = op.prev; OutPt opNext = op.next; while (op != opPrev && PointsEqual(op.pt, opPrev.pt)) opPrev = opPrev.prev; while (op != opNext && PointsEqual(op.pt, opNext.pt)) opNext = opNext.next; IntPoint vec1 = new IntPoint(op.pt.X - opPrev.pt.X, op.pt.Y - opPrev.pt.Y); IntPoint vec2 = new IntPoint(opNext.pt.X - op.pt.X, opNext.pt.Y - op.pt.Y); if (UseFull64BitRange) { Int128 cross = Int128.Int128Mul(vec1.X, vec2.Y) - Int128.Int128Mul(vec2.X, vec1.Y); return !cross.IsNegative(); } else { return (vec1.X * vec2.Y - vec2.X * vec1.Y) > 0; } }
//------------------------------------------------------------------------------ private void SetHoleState(TEdge e, OutRec outRec) { bool isHole = false; TEdge e2 = e.prevInAEL; while (e2 != null) { if (e2.outIdx >= 0) { isHole = !isHole; if (outRec.FirstLeft == null) outRec.FirstLeft = m_PolyOuts[e2.outIdx]; } e2 = e2.prevInAEL; } if (isHole) outRec.isHole = true; }
//------------------------------------------------------------------------------ private OutRec CreateOutRec() { OutRec result = new OutRec(); result.idx = -1; result.isHole = false; result.FirstLeft = null; result.pts = null; result.bottomPt = null; result.polyNode = null; m_PolyOuts.Add(result); result.idx = m_PolyOuts.Count - 1; return result; }
//------------------------------------------------------------------------------ internal void FixHoleLinkage(OutRec outRec) { OutRec tmp; if (outRec.bottomPt != null) tmp = m_PolyOuts[outRec.bottomPt.idx].FirstLeft; else tmp = outRec.FirstLeft; if (outRec == tmp) throw new ClipperException("HoleLinkage error"); if (tmp != null) { if (tmp.AppendLink != null) tmp = FindAppendLinkEnd(tmp); if (tmp == outRec) tmp = null; else if (tmp.isHole) { FixHoleLinkage(tmp); tmp = tmp.FirstLeft; } } outRec.FirstLeft = tmp; if (tmp == null) outRec.isHole = false; outRec.AppendLink = null; }
//------------------------------------------------------------------------------ private void UpdateOutPtIdxs(OutRec outrec) { OutPt op = outrec.pts; do { op.idx = outrec.idx; op = op.prev; } while (op != outrec.pts); }
//------------------------------------------------------------------------------ private void CheckHoleLinkages1(OutRec outRec1, OutRec outRec2) { //when a polygon is split into 2 polygons, make sure any holes the original //polygon contained link to the correct polygon ... for (int i = 0; i < m_PolyOuts.Count; ++i) { if (m_PolyOuts[i].isHole && m_PolyOuts[i].bottomPt != null && m_PolyOuts[i].FirstLeft == outRec1 && !PointInPolygon(m_PolyOuts[i].bottomPt.pt, outRec1.pts, m_UseFullRange)) m_PolyOuts[i].FirstLeft = outRec2; } }
//------------------------------------------------------------------------------ internal void FixHoleLinkage(OutRec outRec) { //skip if an outermost polygon or //already already points to the correct FirstLeft ... if (outRec.FirstLeft == null || (outRec.isHole != outRec.FirstLeft.isHole && outRec.FirstLeft.pts != null)) return; OutRec orfl = outRec.FirstLeft; while (orfl != null && ((orfl.isHole == outRec.isHole) || orfl.pts == null)) orfl = orfl.FirstLeft; outRec.FirstLeft = orfl; }
//------------------------------------------------------------------------------ private OutRec CreateOutRec() { OutRec result = new OutRec(); result.idx = -1; result.isHole = false; result.FirstLeft = null; result.AppendLink = null; result.pts = null; result.bottomPt = null; return result; }
//------------------------------------------------------------------------------ protected override void AddLocalMinPoly(Active e1, Active e2, Point pt) { base.AddLocalMinPoly(e1, e2, pt); OutRec locMinOr = e1.OutRec; (locMinOr.Pts as OutPtTri).outrec = locMinOr; UpdateHelper(locMinOr, locMinOr.Pts); if (locMinOr.Flag == OutrecFlag.Outer) { return; } //do 'keyholing' ... Active e = GetRightAdjacentHotEdge(e1); if (e == e2) { e = GetRightAdjacentHotEdge(e2); } if (e == null) { e = GetLeftAdjacentHotEdge(e1); } OutPt botLft = (e.OutRec as OutRecTri).leftOutpt; OutPt botRt = GetOutPt(e); if (botLft == null || botRt.Pt.Y < botLft.Pt.Y) { botLft = botRt; } botRt = InsertPt(botLft.Pt, botLft.Prev); OutRec botOr = (botLft as OutPtTri).outrec; if (botOr.Pts == null) { botOr = botOr.Owner; } OutPt startOp = botOr.Pts; OutPt endOp = startOp.Next; locMinOr.Flag = OutrecFlag.Outer; locMinOr.Owner = null; OutPt locMinLft = locMinOr.Pts; OutPt locMinRt = InsertPt(locMinLft.Pt, locMinLft); //locMinOr will contain the polygon to the right of the join (ascending), //and botOr will contain the polygon to the left of the join (descending). //tail . botRt . locMinRt : locMinRt is joined by botRt tail locMinRt.Next = endOp; endOp.Prev = locMinRt; botRt.Next = locMinRt; locMinRt.Prev = botRt; locMinOr.Pts = locMinRt; //locMinLft . botLft . head : locMinLft joins behind botLft (left) startOp.Next = locMinLft; locMinLft.Prev = startOp; botLft.Prev = locMinLft; locMinLft.Next = botLft; (locMinLft as OutPtTri).outrec = botOr; //ie abreviated update() Update(locMinRt, locMinOr); //updates the outrec for each op //exchange endE's ... e = botOr.EndE; botOr.EndE = locMinOr.EndE; locMinOr.EndE = e; botOr.EndE.OutRec = botOr; locMinOr.EndE.OutRec = locMinOr; //update helper info ... UpdateHelper(locMinOr, locMinRt); UpdateHelper(botOr, botOr.Pts); Triangulate(locMinOr); Triangulate(botOr); }