1 /* 2 * Copyright 2012 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 #ifndef SkOpSegment_DEFINE 8 #define SkOpSegment_DEFINE 9 10 #include "SkOpAngle.h" 11 #include "SkOpSpan.h" 12 #include "SkPathOpsBounds.h" 13 #include "SkPathOpsCurve.h" 14 #include "SkTArray.h" 15 #include "SkTDArray.h" 16 17 #if defined(SK_DEBUG) || !FORCE_RELEASE 18 #include "SkThread.h" 19 #endif 20 21 struct SkCoincidence; 22 class SkPathWriter; 23 24 class SkOpSegment { 25 public: 26 SkOpSegment() { 27 #if defined(SK_DEBUG) || !FORCE_RELEASE 28 fID = sk_atomic_inc(&SkPathOpsDebug::gSegmentID); 29 #endif 30 } 31 32 bool operator<(const SkOpSegment& rh) const { 33 return fBounds.fTop < rh.fBounds.fTop; 34 } 35 36 struct AlignedSpan { 37 double fOldT; 38 double fT; 39 SkPoint fOldPt; 40 SkPoint fPt; 41 const SkOpSegment* fSegment; 42 const SkOpSegment* fOther1; 43 const SkOpSegment* fOther2; 44 }; 45 46 const SkPathOpsBounds& bounds() const { 47 return fBounds; 48 } 49 50 // OPTIMIZE 51 // when the edges are initially walked, they don't automatically get the prior and next 52 // edges assigned to positions t=0 and t=1. Doing that would remove the need for this check, 53 // and would additionally remove the need for similar checks in condition edges. It would 54 // also allow intersection code to assume end of segment intersections (maybe?) 55 bool complete() const { 56 int count = fTs.count(); 57 return count > 1 && fTs[0].fT == 0 && fTs[--count].fT == 1; 58 } 59 60 int count() const { 61 return fTs.count(); 62 } 63 64 bool done() const { 65 SkASSERT(fDoneSpans <= fTs.count()); 66 return fDoneSpans == fTs.count(); 67 } 68 69 bool done(int min) const { 70 return fTs[min].fDone; 71 } 72 73 bool done(const SkOpAngle* angle) const { 74 return done(SkMin32(angle->start(), angle->end())); 75 } 76 77 SkDPoint dPtAtT(double mid) const { 78 return (*CurveDPointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, mid); 79 } 80 81 SkVector dxdy(int index) const { 82 return (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, fTs[index].fT); 83 } 84 85 SkScalar dy(int index) const { 86 return dxdy(index).fY; 87 } 88 89 bool hasMultiples() const { 90 return fMultiples; 91 } 92 93 bool hasSmall() const { 94 return fSmall; 95 } 96 97 bool hasTiny() const { 98 return fTiny; 99 } 100 101 bool intersected() const { 102 return fTs.count() > 0; 103 } 104 105 bool isCanceled(int tIndex) const { 106 return fTs[tIndex].fWindValue == 0 && fTs[tIndex].fOppValue == 0; 107 } 108 109 bool isConnected(int startIndex, int endIndex) const { 110 return fTs[startIndex].fWindSum != SK_MinS32 || fTs[endIndex].fWindSum != SK_MinS32; 111 } 112 113 bool isHorizontal() const { 114 return fBounds.fTop == fBounds.fBottom; 115 } 116 117 bool isVertical() const { 118 return fBounds.fLeft == fBounds.fRight; 119 } 120 121 bool isVertical(int start, int end) const { 122 return (*CurveIsVertical[SkPathOpsVerbToPoints(fVerb)])(fPts, start, end); 123 } 124 125 bool operand() const { 126 return fOperand; 127 } 128 129 int oppSign(const SkOpAngle* angle) const { 130 SkASSERT(angle->segment() == this); 131 return oppSign(angle->start(), angle->end()); 132 } 133 134 int oppSign(int startIndex, int endIndex) const { 135 int result = startIndex < endIndex ? -fTs[startIndex].fOppValue : fTs[endIndex].fOppValue; 136 #if DEBUG_WIND_BUMP 137 SkDebugf("%s oppSign=%d\n", __FUNCTION__, result); 138 #endif 139 return result; 140 } 141 142 int oppSum(int tIndex) const { 143 return fTs[tIndex].fOppSum; 144 } 145 146 int oppSum(const SkOpAngle* angle) const { 147 int lesser = SkMin32(angle->start(), angle->end()); 148 return fTs[lesser].fOppSum; 149 } 150 151 int oppValue(int tIndex) const { 152 return fTs[tIndex].fOppValue; 153 } 154 155 int oppValue(const SkOpAngle* angle) const { 156 int lesser = SkMin32(angle->start(), angle->end()); 157 return fTs[lesser].fOppValue; 158 } 159 160 #if DEBUG_VALIDATE 161 bool oppXor() const { 162 return fOppXor; 163 } 164 #endif 165 166 SkPoint ptAtT(double mid) const { 167 return (*CurvePointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, mid); 168 } 169 170 const SkPoint* pts() const { 171 return fPts; 172 } 173 174 void reset() { 175 init(NULL, (SkPath::Verb) -1, false, false); 176 fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); 177 fTs.reset(); 178 } 179 180 bool reversePoints(const SkPoint& p1, const SkPoint& p2) const; 181 182 void setOppXor(bool isOppXor) { 183 fOppXor = isOppXor; 184 } 185 186 void setUpWinding(int index, int endIndex, int* maxWinding, int* sumWinding) { 187 int deltaSum = spanSign(index, endIndex); 188 *maxWinding = *sumWinding; 189 *sumWinding -= deltaSum; 190 } 191 192 const SkOpSpan& span(int tIndex) const { 193 return fTs[tIndex]; 194 } 195 196 const SkOpAngle* spanToAngle(int tStart, int tEnd) const { 197 SkASSERT(tStart != tEnd); 198 const SkOpSpan& span = fTs[tStart]; 199 return tStart < tEnd ? span.fToAngle : span.fFromAngle; 200 } 201 202 // FIXME: create some sort of macro or template that avoids casting 203 SkOpAngle* spanToAngle(int tStart, int tEnd) { 204 const SkOpAngle* cAngle = (const_cast<const SkOpSegment*>(this))->spanToAngle(tStart, tEnd); 205 return const_cast<SkOpAngle*>(cAngle); 206 } 207 208 int spanSign(const SkOpAngle* angle) const { 209 SkASSERT(angle->segment() == this); 210 return spanSign(angle->start(), angle->end()); 211 } 212 213 int spanSign(int startIndex, int endIndex) const { 214 int result = startIndex < endIndex ? -fTs[startIndex].fWindValue : fTs[endIndex].fWindValue; 215 #if DEBUG_WIND_BUMP 216 SkDebugf("%s spanSign=%d\n", __FUNCTION__, result); 217 #endif 218 return result; 219 } 220 221 double t(int tIndex) const { 222 return fTs[tIndex].fT; 223 } 224 225 double tAtMid(int start, int end, double mid) const { 226 return fTs[start].fT * (1 - mid) + fTs[end].fT * mid; 227 } 228 229 void updatePts(const SkPoint pts[]) { 230 fPts = pts; 231 } 232 233 SkPath::Verb verb() const { 234 return fVerb; 235 } 236 237 int windSum(int tIndex) const { 238 return fTs[tIndex].fWindSum; 239 } 240 241 int windValue(int tIndex) const { 242 return fTs[tIndex].fWindValue; 243 } 244 245 #if defined(SK_DEBUG) || DEBUG_WINDING 246 SkScalar xAtT(int index) const { 247 return xAtT(&fTs[index]); 248 } 249 #endif 250 251 #if DEBUG_VALIDATE 252 bool _xor() const { // FIXME: used only by SkOpAngle::debugValidateLoop() 253 return fXor; 254 } 255 #endif 256 257 const SkPoint& xyAtT(const SkOpSpan* span) const { 258 return span->fPt; 259 } 260 261 const SkPoint& xyAtT(int index) const { 262 return xyAtT(&fTs[index]); 263 } 264 265 #if defined(SK_DEBUG) || DEBUG_WINDING 266 SkScalar yAtT(int index) const { 267 return yAtT(&fTs[index]); 268 } 269 #endif 270 271 const SkOpAngle* activeAngle(int index, int* start, int* end, bool* done, 272 bool* sortable) const; 273 SkPoint activeLeftTop(int* firstT) const; 274 bool activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op); 275 bool activeWinding(int index, int endIndex); 276 void addCubic(const SkPoint pts[4], bool operand, bool evenOdd); 277 void addCurveTo(int start, int end, SkPathWriter* path, bool active) const; 278 void addEndSpan(int endIndex); 279 void addLine(const SkPoint pts[2], bool operand, bool evenOdd); 280 void addOtherT(int index, double otherT, int otherIndex); 281 void addQuad(const SkPoint pts[3], bool operand, bool evenOdd); 282 void addSimpleAngle(int endIndex); 283 int addSelfT(const SkPoint& pt, double newT); 284 void addStartSpan(int endIndex); 285 int addT(SkOpSegment* other, const SkPoint& pt, double newT); 286 void addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); 287 bool addTCoincident(const SkPoint& startPt, const SkPoint& endPt, double endT, 288 SkOpSegment* other); 289 const SkOpSpan* addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, 290 const SkPoint& pt); 291 const SkOpSpan* addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, 292 const SkPoint& pt, const SkPoint& oPt); 293 void alignMultiples(SkTDArray<AlignedSpan>* aligned); 294 bool alignSpan(int index, double thisT, const SkPoint& thisPt); 295 void alignSpanState(int start, int end); 296 bool betweenTs(int lesser, double testT, int greater) const; 297 void blindCancel(const SkCoincidence& coincidence, SkOpSegment* other); 298 void blindCoincident(const SkCoincidence& coincidence, SkOpSegment* other); 299 bool calcAngles(); 300 double calcMissingTEnd(const SkOpSegment* ref, double loEnd, double min, double max, 301 double hiEnd, const SkOpSegment* other, int thisEnd); 302 double calcMissingTStart(const SkOpSegment* ref, double loEnd, double min, double max, 303 double hiEnd, const SkOpSegment* other, int thisEnd); 304 void checkDuplicates(); 305 void checkEnds(); 306 void checkMultiples(); 307 void checkSmall(); 308 bool checkSmall(int index) const; 309 void checkTiny(); 310 int computeSum(int startIndex, int endIndex, SkOpAngle::IncludeType includeType); 311 bool containsPt(const SkPoint& , int index, int endIndex) const; 312 int crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, bool* hitSomething, 313 double mid, bool opp, bool current) const; 314 bool findCoincidentMatch(const SkOpSpan* span, const SkOpSegment* other, int oStart, int oEnd, 315 int step, SkPoint* startPt, SkPoint* endPt, double* endT) const; 316 SkOpSegment* findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd, 317 bool* unsortable, SkPathOp op, int xorMiMask, int xorSuMask); 318 SkOpSegment* findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd, 319 bool* unsortable); 320 SkOpSegment* findNextXor(int* nextStart, int* nextEnd, bool* unsortable); 321 int findExactT(double t, const SkOpSegment* ) const; 322 int findOtherT(double t, const SkOpSegment* ) const; 323 int findT(double t, const SkPoint& , const SkOpSegment* ) const; 324 SkOpSegment* findTop(int* tIndex, int* endIndex, bool* unsortable, bool firstPass); 325 void fixOtherTIndex(); 326 void initWinding(int start, int end, SkOpAngle::IncludeType angleIncludeType); 327 void initWinding(int start, int end, double tHit, int winding, SkScalar hitDx, int oppWind, 328 SkScalar hitOppDx); 329 bool isMissing(double startT, const SkPoint& pt) const; 330 bool isTiny(const SkOpAngle* angle) const; 331 bool joinCoincidence(SkOpSegment* other, double otherT, const SkPoint& otherPt, int step, 332 bool cancel); 333 SkOpSpan* markAndChaseDoneBinary(int index, int endIndex); 334 SkOpSpan* markAndChaseDoneUnary(int index, int endIndex); 335 SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding); 336 SkOpSpan* markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding, 337 const SkOpAngle* angle); 338 void markDone(int index, int winding); 339 void markDoneBinary(int index); 340 void markDoneUnary(int index); 341 bool nextCandidate(int* start, int* end) const; 342 int nextSpan(int from, int step) const; 343 void pinT(const SkPoint& pt, double* t); 344 void setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding, 345 int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding); 346 void sortAngles(); 347 bool subDivide(int start, int end, SkPoint edge[4]) const; 348 bool subDivide(int start, int end, SkDCubic* result) const; 349 void undoneSpan(int* start, int* end); 350 int updateOppWindingReverse(const SkOpAngle* angle) const; 351 int updateWindingReverse(const SkOpAngle* angle) const; 352 static bool UseInnerWinding(int outerWinding, int innerWinding); 353 static bool UseInnerWindingReverse(int outerWinding, int innerWinding); 354 int windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const; 355 int windSum(const SkOpAngle* angle) const; 356 // available for testing only 357 #if defined(SK_DEBUG) || !FORCE_RELEASE 358 int debugID() const { 359 return fID; 360 } 361 #else 362 int debugID() const { 363 return -1; 364 } 365 #endif 366 #if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY 367 void debugShowActiveSpans() const; 368 #endif 369 #if DEBUG_CONCIDENT 370 void debugShowTs(const char* prefix) const; 371 #endif 372 #if DEBUG_SHOW_WINDING 373 int debugShowWindingValues(int slotCount, int ofInterest) const; 374 #endif 375 const SkTDArray<SkOpSpan>& debugSpans() const; 376 void debugValidate() const; 377 // available to testing only 378 const SkOpAngle* debugLastAngle() const; 379 void dumpAngles() const; 380 void dumpContour(int firstID, int lastID) const; 381 void dumpPts() const; 382 void dumpSpans() const; 383 384 private: 385 struct MissingSpan { 386 double fT; 387 double fEndT; 388 SkOpSegment* fSegment; 389 SkOpSegment* fOther; 390 double fOtherT; 391 SkPoint fPt; 392 }; 393 394 const SkOpAngle* activeAngleInner(int index, int* start, int* end, bool* done, 395 bool* sortable) const; 396 const SkOpAngle* activeAngleOther(int index, int* start, int* end, bool* done, 397 bool* sortable) const; 398 bool activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op, 399 int* sumMiWinding, int* sumSuWinding); 400 bool activeWinding(int index, int endIndex, int* sumWinding); 401 void addCancelOutsides(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); 402 void addCoinOutsides(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); 403 SkOpAngle* addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** ); 404 SkOpAngle* addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** ); 405 SkOpAngle* addSingletonAngles(int step); 406 void alignSpan(const SkPoint& newPt, double newT, const SkOpSegment* other, double otherT, 407 const SkOpSegment* other2, SkOpSpan* oSpan, SkTDArray<AlignedSpan>* ); 408 bool betweenPoints(double midT, const SkPoint& pt1, const SkPoint& pt2) const; 409 void bumpCoincidentBlind(bool binary, int index, int last); 410 void bumpCoincidentThis(const SkOpSpan& oTest, bool binary, int* index, 411 SkTArray<SkPoint, true>* outsideTs); 412 void bumpCoincidentOBlind(int index, int last); 413 void bumpCoincidentOther(const SkOpSpan& oTest, int* index, 414 SkTArray<SkPoint, true>* outsideTs); 415 bool bumpSpan(SkOpSpan* span, int windDelta, int oppDelta); 416 bool calcLoopSpanCount(const SkOpSpan& thisSpan, int* smallCounts); 417 bool checkForSmall(const SkOpSpan* span, const SkPoint& pt, double newT, 418 int* less, int* more) const; 419 void checkLinks(const SkOpSpan* , 420 SkTArray<MissingSpan, true>* missingSpans) const; 421 static void CheckOneLink(const SkOpSpan* test, const SkOpSpan* oSpan, 422 const SkOpSpan* oFirst, const SkOpSpan* oLast, 423 const SkOpSpan** missingPtr, 424 SkTArray<MissingSpan, true>* missingSpans); 425 int checkSetAngle(int tIndex) const; 426 void checkSmallCoincidence(const SkOpSpan& span, SkTArray<MissingSpan, true>* ); 427 bool coincidentSmall(const SkPoint& pt, double t, const SkOpSegment* other) const; 428 bool clockwise(int tStart, int tEnd, bool* swap) const; 429 static void ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, 430 SkOpAngle::IncludeType ); 431 static void ComputeOneSumReverse(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, 432 SkOpAngle::IncludeType ); 433 bool containsT(double t, const SkOpSegment* other, double otherT) const; 434 bool decrementSpan(SkOpSpan* span); 435 int findEndSpan(int endIndex) const; 436 int findStartSpan(int startIndex) const; 437 int firstActive(int tIndex) const; 438 const SkOpSpan& firstSpan(const SkOpSpan& thisSpan) const; 439 void init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd); 440 bool inCoincidentSpan(double t, const SkOpSegment* other) const; 441 bool inLoop(const SkOpAngle* baseAngle, int spanCount, int* indexPtr) const; 442 #if OLD_CHASE 443 bool isSimple(int end) const; 444 #else 445 SkOpSegment* isSimple(int* end, int* step); 446 #endif 447 bool isTiny(int index) const; 448 const SkOpSpan& lastSpan(const SkOpSpan& thisSpan) const; 449 void matchWindingValue(int tIndex, double t, bool borrowWind); 450 SkOpSpan* markAndChaseDone(int index, int endIndex, int winding); 451 SkOpSpan* markAndChaseDoneBinary(const SkOpAngle* angle, int winding, int oppWinding); 452 SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, int winding); 453 SkOpSpan* markAndChaseWinding(int index, int endIndex, int winding); 454 SkOpSpan* markAndChaseWinding(int index, int endIndex, int winding, int oppWinding); 455 SkOpSpan* markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle); 456 void markDoneBinary(int index, int winding, int oppWinding); 457 SkOpSpan* markAndChaseDoneUnary(const SkOpAngle* angle, int winding); 458 void markOneDone(const char* funName, int tIndex, int winding); 459 void markOneDoneBinary(const char* funName, int tIndex); 460 void markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding); 461 void markOneDoneUnary(const char* funName, int tIndex); 462 SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding); 463 SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding, int oppWinding); 464 void markWinding(int index, int winding); 465 void markWinding(int index, int winding, int oppWinding); 466 bool monotonicInY(int tStart, int tEnd) const; 467 468 bool multipleEnds() const { return fTs[count() - 2].fT == 1; } 469 bool multipleStarts() const { return fTs[1].fT == 0; } 470 471 SkOpSegment* nextChase(int* index, int* step, int* min, SkOpSpan** last); 472 int nextExactSpan(int from, int step) const; 473 bool serpentine(int tStart, int tEnd) const; 474 void setCoincidentRange(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); 475 void setFromAngle(int endIndex, SkOpAngle* ); 476 void setToAngle(int endIndex, SkOpAngle* ); 477 void setUpWindings(int index, int endIndex, int* sumMiWinding, 478 int* maxWinding, int* sumWinding); 479 void subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const; 480 static void TrackOutsidePair(SkTArray<SkPoint, true>* outsideTs, const SkPoint& endPt, 481 const SkPoint& startPt); 482 static void TrackOutside(SkTArray<SkPoint, true>* outsideTs, const SkPoint& startPt); 483 int updateOppWinding(int index, int endIndex) const; 484 int updateOppWinding(const SkOpAngle* angle) const; 485 int updateWinding(int index, int endIndex) const; 486 int updateWinding(const SkOpAngle* angle) const; 487 int updateWindingReverse(int index, int endIndex) const; 488 SkOpSpan* verifyOneWinding(const char* funName, int tIndex); 489 SkOpSpan* verifyOneWindingU(const char* funName, int tIndex); 490 491 SkScalar xAtT(const SkOpSpan* span) const { 492 return xyAtT(span).fX; 493 } 494 495 SkScalar yAtT(const SkOpSpan* span) const { 496 return xyAtT(span).fY; 497 } 498 499 void zeroSpan(SkOpSpan* span); 500 501 #if DEBUG_SWAP_TOP 502 bool controlsContainedByEnds(int tStart, int tEnd) const; 503 #endif 504 void debugAddAngle(int start, int end); 505 #if DEBUG_CONCIDENT 506 void debugAddTPair(double t, const SkOpSegment& other, double otherT) const; 507 #endif 508 #if DEBUG_ANGLE 509 void debugCheckPointsEqualish(int tStart, int tEnd) const; 510 #endif 511 #if DEBUG_SWAP_TOP 512 int debugInflections(int index, int endIndex) const; 513 #endif 514 #if DEBUG_MARK_DONE || DEBUG_UNSORTABLE 515 void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding); 516 void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding, int oppWinding); 517 #endif 518 #if DEBUG_WINDING 519 static char as_digit(int value) { 520 return value < 0 ? '?' : value <= 9 ? '0' + value : '+'; 521 } 522 #endif 523 // available to testing only 524 void debugConstruct(); 525 void debugConstructCubic(SkPoint shortQuad[4]); 526 void debugConstructLine(SkPoint shortQuad[2]); 527 void debugConstructQuad(SkPoint shortQuad[3]); 528 void debugReset(); 529 void dumpDPts() const; 530 void dumpSpan(int index) const; 531 532 const SkPoint* fPts; 533 SkPathOpsBounds fBounds; 534 // FIXME: can't convert to SkTArray because it uses insert 535 SkTDArray<SkOpSpan> fTs; // 2+ (always includes t=0 t=1) -- at least (number of spans) + 1 536 SkOpAngleSet fAngles; // empty or 2+ -- (number of non-zero spans) * 2 537 // OPTIMIZATION: could pack donespans, verb, operand, xor into 1 int-sized value 538 int fDoneSpans; // quick check that segment is finished 539 // OPTIMIZATION: force the following to be byte-sized 540 SkPath::Verb fVerb; 541 bool fLoop; // set if cubic intersects itself 542 bool fMultiples; // set if curve intersects multiple other curves at one interior point 543 bool fOperand; 544 bool fXor; // set if original contour had even-odd fill 545 bool fOppXor; // set if opposite operand had even-odd fill 546 bool fSmall; // set if some span is small 547 bool fTiny; // set if some span is tiny 548 #if defined(SK_DEBUG) || !FORCE_RELEASE 549 int fID; 550 #endif 551 552 friend class PathOpsSegmentTester; 553 }; 554 555 #endif 556