@@ -1121,7 +1121,7 @@ internal void SetZValues(BSpline original, Plane projectionPlane)
11211121 if ( zMax < p . z ) zMax = p . z ;
11221122 }
11231123 }
1124- protected override void GetTriangulationBasis ( out GeoPoint2D [ ] points , out GeoVector2D [ ] directions , out double [ ] parameters )
1124+ private double [ ] GetTriangulationKnots ( )
11251125 {
11261126 double [ ] tknots = knots ;
11271127 if ( knots . Length == 2 )
@@ -1131,6 +1131,37 @@ protected override void GetTriangulationBasis(out GeoPoint2D[] points, out GeoVe
11311131 tknots [ 1 ] = ( knots [ 0 ] + knots [ 1 ] ) / 2.0 ;
11321132 tknots [ 2 ] = knots [ 1 ] ;
11331133 }
1134+ // The triangulation based HitTest relies on each span's control triangle enclosing the
1135+ // curve, which only holds while a span turns less than ~180°. Free-form splines with few
1136+ // knots have spans that turn too much, so the coarse triangle test rejects them and only
1137+ // the knot points (notably the curve start/end) register as hits. Subdivide such spans by
1138+ // turning angle so the resulting triangles stay tight enough to bound the curve.
1139+ const double maxSpanAngle = Math . PI / 4.0 ; // 45° per sub-span
1140+ List < double > res = new List < double > ( tknots . Length ) ;
1141+ GeoVector2D d0 = DirectionAtParam ( tknots [ 0 ] ) ;
1142+ for ( int i = 0 ; i < tknots . Length - 1 ; i ++ )
1143+ {
1144+ res . Add ( tknots [ i ] ) ;
1145+ GeoVector2D d1 = DirectionAtParam ( tknots [ i + 1 ] ) ;
1146+ if ( ! d0 . IsNullVector ( ) && ! d1 . IsNullVector ( ) )
1147+ {
1148+ double dot = d0 . x * d1 . x + d0 . y * d1 . y ;
1149+ double cross = d0 . x * d1 . y - d0 . y * d1 . x ;
1150+ double angle = Math . Atan2 ( Math . Abs ( cross ) , dot ) ; // [0, pi], directions are normalized
1151+ int sub = ( int ) Math . Ceiling ( angle / maxSpanAngle ) ;
1152+ for ( int j = 1 ; j < sub ; j ++ )
1153+ {
1154+ res . Add ( tknots [ i ] + ( tknots [ i + 1 ] - tknots [ i ] ) * j / sub ) ;
1155+ }
1156+ }
1157+ d0 = d1 ;
1158+ }
1159+ res . Add ( tknots [ tknots . Length - 1 ] ) ;
1160+ return res . ToArray ( ) ;
1161+ }
1162+ protected override void GetTriangulationBasis ( out GeoPoint2D [ ] points , out GeoVector2D [ ] directions , out double [ ] parameters )
1163+ {
1164+ double [ ] tknots = GetTriangulationKnots ( ) ;
11341165 points = new GeoPoint2D [ tknots . Length ] ;
11351166 directions = new GeoVector2D [ tknots . Length ] ;
11361167 parameters = new double [ tknots . Length ] ;
@@ -1142,14 +1173,7 @@ protected override void GetTriangulationBasis(out GeoPoint2D[] points, out GeoVe
11421173 }
11431174 internal override void GetTriangulationPoints ( out GeoPoint2D [ ] points , out double [ ] parameters )
11441175 {
1145- double [ ] tknots = knots ;
1146- if ( knots . Length == 2 )
1147- { // there are splines with only two knots and degree 14 which represent a full circle. We invent an additional knot
1148- tknots = new double [ 3 ] ;
1149- tknots [ 0 ] = knots [ 0 ] ;
1150- tknots [ 1 ] = ( knots [ 0 ] + knots [ 1 ] ) / 2.0 ;
1151- tknots [ 2 ] = knots [ 1 ] ;
1152- }
1176+ double [ ] tknots = GetTriangulationKnots ( ) ;
11531177 points = new GeoPoint2D [ tknots . Length ] ;
11541178 parameters = new double [ tknots . Length ] ;
11551179 for ( int i = 0 ; i < tknots . Length ; i ++ )
0 commit comments