1 //-----------------------------------------------------------------------------
2 // Routines for ray-casting: intersecting a line segment or an infinite line
3 // with a surface or shell. Ray-casting against a shell is used for point-in-
4 // shell testing, and the intersection of edge line segments against surfaces
5 // is used to get rough surface-curve intersections, which are later refined
6 // numerically.
7 //
8 // Copyright 2008-2013 Jonathan Westhues.
9 //-----------------------------------------------------------------------------
10 #include "solvespace.h"
11
12 // Dot product tolerance for perpendicular; this is on the direction cosine,
13 // so it's about 0.001 degrees.
14 const double SShell::DOTP_TOL = 1e-5;
15
16 extern int FLAG;
17
18
DepartureFromCoplanar(void)19 double SSurface::DepartureFromCoplanar(void) {
20 int i, j;
21 int ia, ja, ib = 0, jb = 0, ic = 0, jc = 0;
22 double best;
23
24 // Grab three points to define a plane; first choose (0, 0) arbitrarily.
25 ia = ja = 0;
26 // Then the point farthest from pt a.
27 best = VERY_NEGATIVE;
28 for(i = 0; i <= degm; i++) {
29 for(j = 0; j <= degn; j++) {
30 if(i == ia && j == ja) continue;
31
32 double dist = (ctrl[i][j]).Minus(ctrl[ia][ja]).Magnitude();
33 if(dist > best) {
34 best = dist;
35 ib = i;
36 jb = j;
37 }
38 }
39 }
40 // Then biggest magnitude of ab cross ac.
41 best = VERY_NEGATIVE;
42 for(i = 0; i <= degm; i++) {
43 for(j = 0; j <= degn; j++) {
44 if(i == ia && j == ja) continue;
45 if(i == ib && j == jb) continue;
46
47 double mag =
48 ((ctrl[ia][ja].Minus(ctrl[ib][jb]))).Cross(
49 (ctrl[ia][ja].Minus(ctrl[i ][j ]))).Magnitude();
50 if(mag > best) {
51 best = mag;
52 ic = i;
53 jc = j;
54 }
55 }
56 }
57
58 Vector n = ((ctrl[ia][ja].Minus(ctrl[ib][jb]))).Cross(
59 (ctrl[ia][ja].Minus(ctrl[ic][jc])));
60 n = n.WithMagnitude(1);
61 double d = (ctrl[ia][ja]).Dot(n);
62
63 // Finally, calculate the deviation from each point to the plane.
64 double farthest = VERY_NEGATIVE;
65 for(i = 0; i <= degm; i++) {
66 for(j = 0; j <= degn; j++) {
67 double dist = fabs(n.Dot(ctrl[i][j]) - d);
68 if(dist > farthest) {
69 farthest = dist;
70 }
71 }
72 }
73 return farthest;
74 }
75
WeightControlPoints(void)76 void SSurface::WeightControlPoints(void) {
77 int i, j;
78 for(i = 0; i <= degm; i++) {
79 for(j = 0; j <= degn; j++) {
80 ctrl[i][j] = (ctrl[i][j]).ScaledBy(weight[i][j]);
81 }
82 }
83 }
UnWeightControlPoints(void)84 void SSurface::UnWeightControlPoints(void) {
85 int i, j;
86 for(i = 0; i <= degm; i++) {
87 for(j = 0; j <= degn; j++) {
88 ctrl[i][j] = (ctrl[i][j]).ScaledBy(1.0/weight[i][j]);
89 }
90 }
91 }
CopyRowOrCol(bool row,int this_ij,SSurface * src,int src_ij)92 void SSurface::CopyRowOrCol(bool row, int this_ij, SSurface *src, int src_ij) {
93 if(row) {
94 int j;
95 for(j = 0; j <= degn; j++) {
96 ctrl [this_ij][j] = src->ctrl [src_ij][j];
97 weight[this_ij][j] = src->weight[src_ij][j];
98 }
99 } else {
100 int i;
101 for(i = 0; i <= degm; i++) {
102 ctrl [i][this_ij] = src->ctrl [i][src_ij];
103 weight[i][this_ij] = src->weight[i][src_ij];
104 }
105 }
106 }
BlendRowOrCol(bool row,int this_ij,SSurface * a,int a_ij,SSurface * b,int b_ij)107 void SSurface::BlendRowOrCol(bool row, int this_ij, SSurface *a, int a_ij,
108 SSurface *b, int b_ij)
109 {
110 if(row) {
111 int j;
112 for(j = 0; j <= degn; j++) {
113 Vector c = (a->ctrl [a_ij][j]).Plus(b->ctrl [b_ij][j]);
114 double w = (a->weight[a_ij][j] + b->weight[b_ij][j]);
115 ctrl [this_ij][j] = c.ScaledBy(0.5);
116 weight[this_ij][j] = w / 2;
117 }
118 } else {
119 int i;
120 for(i = 0; i <= degm; i++) {
121 Vector c = (a->ctrl [i][a_ij]).Plus(b->ctrl [i][b_ij]);
122 double w = (a->weight[i][a_ij] + b->weight[i][b_ij]);
123 ctrl [i][this_ij] = c.ScaledBy(0.5);
124 weight[i][this_ij] = w / 2;
125 }
126 }
127 }
SplitInHalf(bool byU,SSurface * sa,SSurface * sb)128 void SSurface::SplitInHalf(bool byU, SSurface *sa, SSurface *sb) {
129 sa->degm = sb->degm = degm;
130 sa->degn = sb->degn = degn;
131
132 // by de Casteljau's algorithm in a projective space; so we must work
133 // on points (w*x, w*y, w*z, w)
134 WeightControlPoints();
135
136 switch(byU ? degm : degn) {
137 case 1:
138 sa->CopyRowOrCol (byU, 0, this, 0);
139 sb->CopyRowOrCol (byU, 1, this, 1);
140
141 sa->BlendRowOrCol(byU, 1, this, 0, this, 1);
142 sb->BlendRowOrCol(byU, 0, this, 0, this, 1);
143 break;
144
145 case 2:
146 sa->CopyRowOrCol (byU, 0, this, 0);
147 sb->CopyRowOrCol (byU, 2, this, 2);
148
149 sa->BlendRowOrCol(byU, 1, this, 0, this, 1);
150 sb->BlendRowOrCol(byU, 1, this, 1, this, 2);
151
152 sa->BlendRowOrCol(byU, 2, sa, 1, sb, 1);
153 sb->BlendRowOrCol(byU, 0, sa, 1, sb, 1);
154 break;
155
156 case 3: {
157 SSurface st;
158 st.degm = degm; st.degn = degn;
159
160 sa->CopyRowOrCol (byU, 0, this, 0);
161 sb->CopyRowOrCol (byU, 3, this, 3);
162
163 sa->BlendRowOrCol(byU, 1, this, 0, this, 1);
164 sb->BlendRowOrCol(byU, 2, this, 2, this, 3);
165 st. BlendRowOrCol(byU, 0, this, 1, this, 2); // scratch var
166
167 sa->BlendRowOrCol(byU, 2, sa, 1, &st, 0);
168 sb->BlendRowOrCol(byU, 1, sb, 2, &st, 0);
169
170 sa->BlendRowOrCol(byU, 3, sa, 2, sb, 1);
171 sb->BlendRowOrCol(byU, 0, sa, 2, sb, 1);
172 break;
173 }
174
175 default: oops();
176 }
177
178 sa->UnWeightControlPoints();
179 sb->UnWeightControlPoints();
180 UnWeightControlPoints();
181 }
182
183 //-----------------------------------------------------------------------------
184 // Find all points where the indicated finite (if segment) or infinite (if not
185 // segment) line intersects our surface. Report them in uv space in the list.
186 // We first do a bounding box check; if the line doesn't intersect, then we're
187 // done. If it does, then we check how small our surface is. If it's big,
188 // then we subdivide into quarters and recurse. If it's small, then we refine
189 // by Newton's method and record the point.
190 //-----------------------------------------------------------------------------
AllPointsIntersectingUntrimmed(Vector a,Vector b,int * cnt,int * level,List<Inter> * l,bool segment,SSurface * sorig)191 void SSurface::AllPointsIntersectingUntrimmed(Vector a, Vector b,
192 int *cnt, int *level,
193 List<Inter> *l, bool segment,
194 SSurface *sorig)
195 {
196 // Test if the line intersects our axis-aligned bounding box; if no, then
197 // no possibility of an intersection
198 if(LineEntirelyOutsideBbox(a, b, segment)) return;
199
200 if(*cnt > 2000) {
201 dbp("!!! too many subdivisions (level=%d)!", *level);
202 dbp("degm = %d degn = %d", degm, degn);
203 return;
204 }
205 (*cnt)++;
206
207 // If we might intersect, and the surface is small, then switch to Newton
208 // iterations.
209 if(DepartureFromCoplanar() < 0.2*SS.ChordTolMm()) {
210 Vector p = (ctrl[0 ][0 ]).Plus(
211 ctrl[0 ][degn]).Plus(
212 ctrl[degm][0 ]).Plus(
213 ctrl[degm][degn]).ScaledBy(0.25);
214 Inter inter;
215 sorig->ClosestPointTo(p, &(inter.p.x), &(inter.p.y), false);
216 if(sorig->PointIntersectingLine(a, b, &(inter.p.x), &(inter.p.y))) {
217 Vector p = sorig->PointAt(inter.p.x, inter.p.y);
218 // Debug check, verify that the point lies in both surfaces
219 // (which it ought to, since the surfaces should be coincident)
220 double u, v;
221 ClosestPointTo(p, &u, &v);
222 l->Add(&inter);
223 } else {
224 // Might not converge if line is almost tangent to surface...
225 }
226 return;
227 }
228
229 // But the surface is big, so split it, alternating by u and v
230 SSurface surf0, surf1;
231 SplitInHalf((*level & 1) == 0, &surf0, &surf1);
232
233 int nextLevel = (*level) + 1;
234 (*level) = nextLevel;
235 surf0.AllPointsIntersectingUntrimmed(a, b, cnt, level, l, segment, sorig);
236 (*level) = nextLevel;
237 surf1.AllPointsIntersectingUntrimmed(a, b, cnt, level, l, segment, sorig);
238 }
239
240 //-----------------------------------------------------------------------------
241 // Find all points where a line through a and b intersects our surface, and
242 // add them to the list. If seg is true then report only intersections that
243 // lie within the finite line segment (not including the endpoints); otherwise
244 // we work along the infinite line. And we report either just intersections
245 // inside the trim curve, or any intersection with u, v in [0, 1]. And we
246 // either disregard or report tangent points.
247 //-----------------------------------------------------------------------------
AllPointsIntersecting(Vector a,Vector b,List<SInter> * l,bool seg,bool trimmed,bool inclTangent)248 void SSurface::AllPointsIntersecting(Vector a, Vector b,
249 List<SInter> *l,
250 bool seg, bool trimmed, bool inclTangent)
251 {
252 if(LineEntirelyOutsideBbox(a, b, seg)) return;
253
254 Vector ba = b.Minus(a);
255 double bam = ba.Magnitude();
256
257 List<Inter> inters = {};
258
259 // All the intersections between the line and the surface; either special
260 // cases that we can quickly solve in closed form, or general numerical.
261 Vector center, axis, start, finish;
262 double radius;
263 if(degm == 1 && degn == 1) {
264 // Against a plane, easy.
265 Vector n = NormalAt(0, 0).WithMagnitude(1);
266 double d = n.Dot(PointAt(0, 0));
267 // Trim to line segment now if requested, don't generate points that
268 // would just get discarded later.
269 if(!seg ||
270 (n.Dot(a) > d + LENGTH_EPS && n.Dot(b) < d - LENGTH_EPS) ||
271 (n.Dot(b) > d + LENGTH_EPS && n.Dot(a) < d - LENGTH_EPS))
272 {
273 Vector p = Vector::AtIntersectionOfPlaneAndLine(n, d, a, b, NULL);
274 Inter inter;
275 ClosestPointTo(p, &(inter.p.x), &(inter.p.y));
276 inters.Add(&inter);
277 }
278 } else if(IsCylinder(&axis, ¢er, &radius, &start, &finish)) {
279 // This one can be solved in closed form too.
280 Vector ab = b.Minus(a);
281 if(axis.Cross(ab).Magnitude() < LENGTH_EPS) {
282 // edge is parallel to axis of cylinder, no intersection points
283 return;
284 }
285 // A coordinate system centered at the center of the circle, with
286 // the edge under test horizontal
287 Vector u, v, n = axis.WithMagnitude(1);
288 u = (ab.Minus(n.ScaledBy(ab.Dot(n)))).WithMagnitude(1);
289 v = n.Cross(u);
290 Point2d ap = (a.Minus(center)).DotInToCsys(u, v, n).ProjectXy(),
291 bp = (b.Minus(center)).DotInToCsys(u, v, n).ProjectXy(),
292 sp = (start. Minus(center)).DotInToCsys(u, v, n).ProjectXy(),
293 fp = (finish.Minus(center)).DotInToCsys(u, v, n).ProjectXy();
294
295 double thetas = atan2(sp.y, sp.x), thetaf = atan2(fp.y, fp.x);
296
297 Point2d ip[2];
298 int ip_n = 0;
299 if(fabs(fabs(ap.y) - radius) < LENGTH_EPS) {
300 // tangent
301 if(inclTangent) {
302 ip[0] = Point2d::From(0, ap.y);
303 ip_n = 1;
304 }
305 } else if(fabs(ap.y) < radius) {
306 // two intersections
307 double xint = sqrt(radius*radius - ap.y*ap.y);
308 ip[0] = Point2d::From(-xint, ap.y);
309 ip[1] = Point2d::From( xint, ap.y);
310 ip_n = 2;
311 }
312 int i;
313 for(i = 0; i < ip_n; i++) {
314 double t = (ip[i].Minus(ap)).DivPivoting(bp.Minus(ap));
315 // This is a point on the circle; but is it on the arc?
316 Point2d pp = ap.Plus((bp.Minus(ap)).ScaledBy(t));
317 double theta = atan2(pp.y, pp.x);
318 double dp = WRAP_SYMMETRIC(theta - thetas, 2*PI),
319 df = WRAP_SYMMETRIC(thetaf - thetas, 2*PI);
320 double tol = LENGTH_EPS/radius;
321
322 if((df > 0 && ((dp < -tol) || (dp > df + tol))) ||
323 (df < 0 && ((dp > tol) || (dp < df - tol))))
324 {
325 continue;
326 }
327
328 Vector p = a.Plus((b.Minus(a)).ScaledBy(t));
329
330 Inter inter;
331 ClosestPointTo(p, &(inter.p.x), &(inter.p.y));
332 inters.Add(&inter);
333 }
334 } else {
335 // General numerical solution by subdivision, fallback
336 int cnt = 0, level = 0;
337 AllPointsIntersectingUntrimmed(a, b, &cnt, &level, &inters, seg, this);
338 }
339
340 // Remove duplicate intersection points
341 inters.ClearTags();
342 int i, j;
343 for(i = 0; i < inters.n; i++) {
344 for(j = i + 1; j < inters.n; j++) {
345 if(inters.elem[i].p.Equals(inters.elem[j].p)) {
346 inters.elem[j].tag = 1;
347 }
348 }
349 }
350 inters.RemoveTagged();
351
352 for(i = 0; i < inters.n; i++) {
353 Point2d puv = inters.elem[i].p;
354
355 // Make sure the point lies within the finite line segment
356 Vector pxyz = PointAt(puv.x, puv.y);
357 double t = (pxyz.Minus(a)).DivPivoting(ba);
358 if(seg && (t > 1 - LENGTH_EPS/bam || t < LENGTH_EPS/bam)) {
359 continue;
360 }
361
362 // And that it lies inside our trim region
363 Point2d dummy = { 0, 0 };
364 int c = (bsp) ? bsp->ClassifyPoint(puv, dummy, this) : SBspUv::OUTSIDE;
365 if(trimmed && c == SBspUv::OUTSIDE) {
366 continue;
367 }
368
369 // It does, so generate the intersection
370 SInter si;
371 si.p = pxyz;
372 si.surfNormal = NormalAt(puv.x, puv.y);
373 si.pinter = puv;
374 si.srf = this;
375 si.onEdge = (c != SBspUv::INSIDE);
376 l->Add(&si);
377 }
378
379 inters.Clear();
380 }
381
AllPointsIntersecting(Vector a,Vector b,List<SInter> * il,bool seg,bool trimmed,bool inclTangent)382 void SShell::AllPointsIntersecting(Vector a, Vector b,
383 List<SInter> *il,
384 bool seg, bool trimmed, bool inclTangent)
385 {
386 SSurface *ss;
387 for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) {
388 ss->AllPointsIntersecting(a, b, il, seg, trimmed, inclTangent);
389 }
390 }
391
392
393
ClassifyRegion(Vector edge_n,Vector inter_surf_n,Vector edge_surf_n)394 int SShell::ClassifyRegion(Vector edge_n, Vector inter_surf_n,
395 Vector edge_surf_n)
396 {
397 double dot = inter_surf_n.DirectionCosineWith(edge_n);
398 if(fabs(dot) < DOTP_TOL) {
399 // The edge's surface and the edge-on-face surface
400 // are coincident. Test the edge's surface normal
401 // to see if it's with same or opposite normals.
402 if(inter_surf_n.Dot(edge_surf_n) > 0) {
403 return COINC_SAME;
404 } else {
405 return COINC_OPP;
406 }
407 } else if(dot > 0) {
408 return OUTSIDE;
409 } else {
410 return INSIDE;
411 }
412 }
413
414 //-----------------------------------------------------------------------------
415 // Does the given point lie on our shell? There are many cases; inside and
416 // outside are obvious, but then there's all the edge-on-edge and edge-on-face
417 // possibilities.
418 //
419 // To calculate, we intersect a ray through p with our shell, and classify
420 // using the closest intersection point. If the ray hits a surface on edge,
421 // then just reattempt in a different random direction.
422 //-----------------------------------------------------------------------------
ClassifyEdge(int * indir,int * outdir,Vector ea,Vector eb,Vector p,Vector edge_n_in,Vector edge_n_out,Vector surf_n)423 bool SShell::ClassifyEdge(int *indir, int *outdir,
424 Vector ea, Vector eb,
425 Vector p,
426 Vector edge_n_in, Vector edge_n_out, Vector surf_n)
427 {
428 List<SInter> l = {};
429
430 srand(0);
431
432 // First, check for edge-on-edge
433 int edge_inters = 0;
434 Vector inter_surf_n[2], inter_edge_n[2];
435 SSurface *srf;
436 for(srf = surface.First(); srf; srf = surface.NextAfter(srf)) {
437 if(srf->LineEntirelyOutsideBbox(ea, eb, true)) continue;
438
439 SEdgeList *sel = &(srf->edges);
440 SEdge *se;
441 for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
442 if((ea.Equals(se->a) && eb.Equals(se->b)) ||
443 (eb.Equals(se->a) && ea.Equals(se->b)) ||
444 p.OnLineSegment(se->a, se->b))
445 {
446 if(edge_inters < 2) {
447 // Edge-on-edge case
448 Point2d pm;
449 srf->ClosestPointTo(p, &pm, false);
450 // A vector normal to the surface, at the intersection point
451 inter_surf_n[edge_inters] = srf->NormalAt(pm);
452 // A vector normal to the intersecting edge (but within the
453 // intersecting surface) at the intersection point, pointing
454 // out.
455 inter_edge_n[edge_inters] =
456 (inter_surf_n[edge_inters]).Cross((se->b).Minus((se->a)));
457 }
458
459 edge_inters++;
460 }
461 }
462 }
463
464 if(edge_inters == 2) {
465 // TODO, make this use the appropriate curved normals
466 double dotp[2];
467 for(int i = 0; i < 2; i++) {
468 dotp[i] = edge_n_out.DirectionCosineWith(inter_surf_n[i]);
469 }
470
471 if(fabs(dotp[1]) < DOTP_TOL) {
472 swap(dotp[0], dotp[1]);
473 swap(inter_surf_n[0], inter_surf_n[1]);
474 swap(inter_edge_n[0], inter_edge_n[1]);
475 }
476
477 int coinc = (surf_n.Dot(inter_surf_n[0])) > 0 ? COINC_SAME : COINC_OPP;
478
479 if(fabs(dotp[0]) < DOTP_TOL && fabs(dotp[1]) < DOTP_TOL) {
480 // This is actually an edge on face case, just that the face
481 // is split into two pieces joining at our edge.
482 *indir = coinc;
483 *outdir = coinc;
484 } else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] > DOTP_TOL) {
485 if(edge_n_out.Dot(inter_edge_n[0]) > 0) {
486 *indir = coinc;
487 *outdir = OUTSIDE;
488 } else {
489 *indir = INSIDE;
490 *outdir = coinc;
491 }
492 } else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] < -DOTP_TOL) {
493 if(edge_n_out.Dot(inter_edge_n[0]) > 0) {
494 *indir = coinc;
495 *outdir = INSIDE;
496 } else {
497 *indir = OUTSIDE;
498 *outdir = coinc;
499 }
500 } else if(dotp[0] > DOTP_TOL && dotp[1] > DOTP_TOL) {
501 *indir = INSIDE;
502 *outdir = OUTSIDE;
503 } else if(dotp[0] < -DOTP_TOL && dotp[1] < -DOTP_TOL) {
504 *indir = OUTSIDE;
505 *outdir = INSIDE;
506 } else {
507 // Edge is tangent to the shell at shell's edge, so can't be
508 // a boundary of the surface.
509 return false;
510 }
511 return true;
512 }
513
514 if(edge_inters != 0) dbp("bad, edge_inters=%d", edge_inters);
515
516 // Next, check for edge-on-surface. The ray-casting for edge-inside-shell
517 // would catch this too, but test separately, for speed (since many edges
518 // are on surface) and for numerical stability, so we don't pick up
519 // the additional error from the line intersection.
520
521 for(srf = surface.First(); srf; srf = surface.NextAfter(srf)) {
522 if(srf->LineEntirelyOutsideBbox(ea, eb, true)) continue;
523
524 Point2d puv;
525 srf->ClosestPointTo(p, &(puv.x), &(puv.y), false);
526 Vector pp = srf->PointAt(puv);
527
528 if((pp.Minus(p)).Magnitude() > LENGTH_EPS) continue;
529 Point2d dummy = { 0, 0 };
530 int c = (srf->bsp) ? srf->bsp->ClassifyPoint(puv, dummy, srf) : SBspUv::OUTSIDE;
531 if(c == SBspUv::OUTSIDE) continue;
532
533 // Edge-on-face (unless edge-on-edge above superceded)
534 Point2d pin, pout;
535 srf->ClosestPointTo(p.Plus(edge_n_in), &pin, false);
536 srf->ClosestPointTo(p.Plus(edge_n_out), &pout, false);
537
538 Vector surf_n_in = srf->NormalAt(pin),
539 surf_n_out = srf->NormalAt(pout);
540
541 *indir = ClassifyRegion(edge_n_in, surf_n_in, surf_n);
542 *outdir = ClassifyRegion(edge_n_out, surf_n_out, surf_n);
543 return true;
544 }
545
546 // Edge is not on face or on edge; so it's either inside or outside
547 // the shell, and we'll determine which by raycasting.
548 int cnt = 0;
549 for(;;) {
550 // Cast a ray in a random direction (two-sided so that we test if
551 // the point lies on a surface, but use only one side for in/out
552 // testing)
553 Vector ray = Vector::From(Random(1), Random(1), Random(1));
554
555 AllPointsIntersecting(
556 p.Minus(ray), p.Plus(ray), &l, false, true, false);
557
558 // no intersections means it's outside
559 *indir = OUTSIDE;
560 *outdir = OUTSIDE;
561 double dmin = VERY_POSITIVE;
562 bool onEdge = false;
563 edge_inters = 0;
564
565 SInter *si;
566 for(si = l.First(); si; si = l.NextAfter(si)) {
567 double t = ((si->p).Minus(p)).DivPivoting(ray);
568 if(t*ray.Magnitude() < -LENGTH_EPS) {
569 // wrong side, doesn't count
570 continue;
571 }
572
573 double d = ((si->p).Minus(p)).Magnitude();
574
575 // We actually should never hit this case; it should have been
576 // handled above.
577 if(d < LENGTH_EPS && si->onEdge) {
578 edge_inters++;
579 }
580
581 if(d < dmin) {
582 dmin = d;
583 // Edge does not lie on surface; either strictly inside
584 // or strictly outside
585 if((si->surfNormal).Dot(ray) > 0) {
586 *indir = INSIDE;
587 *outdir = INSIDE;
588 } else {
589 *indir = OUTSIDE;
590 *outdir = OUTSIDE;
591 }
592 onEdge = si->onEdge;
593 }
594 }
595 l.Clear();
596
597 // If the point being tested lies exactly on an edge of the shell,
598 // then our ray always lies on edge, and that's okay. Otherwise
599 // try again in a different random direction.
600 if(!onEdge) break;
601 if(cnt++ > 5) {
602 dbp("can't find a ray that doesn't hit on edge!");
603 dbp("on edge = %d, edge_inters = %d", onEdge, edge_inters);
604 SS.nakedEdges.AddEdge(ea, eb);
605 break;
606 }
607 }
608
609 return true;
610 }
611
612