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, &center, &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