@@ -1073,14 +1073,14 @@ fn winding_number_line(
1073
1073
// An upward segment (-y) increments the winding number (including the initial endpoint).
1074
1074
// A downward segment (+y) decrements the winding number (including the final endpoint)
1075
1075
// Perp-dot indicates which side of the segment the point is on.
1076
- if y0 <= point_y {
1077
- if y1 > point_y {
1076
+ if y0 < point_y {
1077
+ if y1 >= point_y {
1078
1078
let val = ( x1 - x0) * ( point_y - y0) - ( y1 - y0) * ( point_x - x0) ;
1079
1079
if val > 0.0 {
1080
1080
return 1 ;
1081
1081
}
1082
1082
}
1083
- } else if y1 <= point_y {
1083
+ } else if y1 < point_y {
1084
1084
let val = ( x1 - x0) * ( point_y - y0) - ( y1 - y0) * ( point_x - x0) ;
1085
1085
1086
1086
if val < 0.0 {
@@ -1107,13 +1107,13 @@ fn winding_number_curve(
1107
1107
// However, there are two issues:
1108
1108
// 1) Solving the quadratic needs to be numerically robust, particularly near the endpoints 0.0 and 1.0, and as the curve is tangent to the ray.
1109
1109
// We use the "Citardauq" method for improved numerical stability.
1110
- // 2) The convention for including/excluding endpoints needs to act similarly to lines, with the initial point included if the curve is "downward ",
1111
- // and the final point included if the curve is pointing "upward ". This is complicated by the fact that the curve could be tangent to the ray
1110
+ // 2) The convention for including/excluding endpoints needs to act similarly to lines, with the initial point included if the curve is "upward ",
1111
+ // and the final point included if the curve is pointing "downward ". This is complicated by the fact that the curve could be tangent to the ray
1112
1112
// at the endpoint (this is still considered "upward" or "downward" depending on the slope at earlier t).
1113
1113
// We solve this by splitting the curve into y-monotonic subcurves. This is helpful because
1114
1114
// a) each subcurve will have 1 intersection with the ray
1115
1115
// b) if the subcurve surrounds the ray, we know it has an intersection without having to check if t is in [0, 1]
1116
- // c) we can determine the winding of the segment upward/downward by comparing the subcurve endpoints, also properly handling the endpoint convention.
1116
+ // c) we know the winding of the segment upward/downward based on which root it contains
1117
1117
1118
1118
let x0 = ax0. get ( ) - point_x. get ( ) ;
1119
1119
let y0 = ay0. get ( ) - point_y. get ( ) ;
@@ -1141,63 +1141,68 @@ fn winding_number_curve(
1141
1141
let b = 2.0 * ( y1 - y0) ;
1142
1142
let c = y0;
1143
1143
1144
- let roots = solve_quadratic ( a, b, c) ;
1145
-
1146
- if roots. is_empty ( ) {
1144
+ let ( t0, t1) = solve_quadratic ( a, b, c) ;
1145
+ let is_t0_valid = t0. is_finite ( ) ;
1146
+ let is_t1_valid = t1. is_finite ( ) ;
1147
+ if !is_t0_valid && !is_t1_valid {
1147
1148
return 0 ;
1148
1149
}
1149
1150
1150
1151
// Split the curve into two y-monotonic segments.
1151
- let mut y_start = y0;
1152
- let mut y_end = if roots. len ( ) == 1 {
1153
- // Linear, so already monotonic.
1154
- y2
1155
- } else {
1156
- // Find the extrema point where the curve is horizontal.
1157
- // This is the point that splits the curve into 2 y-monotonic segments.
1158
- let t_extrema = -b / ( 2.0 * a) ;
1159
- a * t_extrema * t_extrema + b * t_extrema + c
1160
- } ;
1161
-
1152
+ let mut winding = 0 ;
1162
1153
let ax = x0 - 2.0 * x1 + x2;
1163
1154
let bx = 2.0 * ( x1 - x0) ;
1155
+ let t_extrema = -0.5 * b / a;
1156
+ let is_monotonic = t_extrema <= 0.0 || t_extrema >= 1.0 ;
1157
+ if a >= 0.0 {
1158
+ // Downward opening parabola.
1159
+ let y_min = if is_monotonic {
1160
+ y0. min ( y2)
1161
+ } else {
1162
+ a * t_extrema * t_extrema + b * t_extrema + c
1163
+ } ;
1164
1164
1165
- let mut winding = 0 ;
1166
- for t in roots {
1167
- // Verify that this monotonic segment straddles the ray, and choose winding direction.
1168
- // This also handles the endpoint conventions.
1169
- let direction = if y_end > y_start {
1170
- // Downward edge: initial point included, increments winding.
1171
- if y_start > 0.0 || y_end <= 0.0 {
1172
- 0
1173
- } else {
1174
- 1
1165
+ // First subcurve is moving upward, include initial point.
1166
+ if is_t0_valid && y0 >= 0.0 && y_min < 0.0 {
1167
+ // If curve point is to the right of the ray origin (x > 0), the ray will hit it.
1168
+ // We don't have to check 0 <= t <= 1 check because we've already guaranteed that the subcurve
1169
+ // straddles the ray.
1170
+ let x = x0 + bx * t0 + ax * t0 * t0;
1171
+ if x > 0.0 {
1172
+ winding += 1 ;
1175
1173
}
1176
- } else if y_start > y_end {
1177
- // Upward edge: final point included, increments winding.
1178
- if y_start <= 0.0 || y_end > 0.0 {
1179
- 0
1180
- } else {
1181
- -1
1174
+ }
1175
+
1176
+ // Second subcurve is moving downard, include final point.
1177
+ if is_t1_valid && y_min < 0.0 && y2 >= 0.0 {
1178
+ let x = x0 + bx * t1 + ax * t1 * t1;
1179
+ if x > 0.0 {
1180
+ winding -= 1 ;
1182
1181
}
1182
+ }
1183
+ } else {
1184
+ // Upward opening parabola.
1185
+ let y_max = if is_monotonic {
1186
+ y0. max ( y2)
1183
1187
} else {
1184
- 0
1188
+ a * t_extrema * t_extrema + b * t_extrema + c
1185
1189
} ;
1186
1190
1187
- // If curve point is to the right of the ray origin, the ray will hit it.
1188
- // We don't have to do the problematic 0 <= t <= 1 check because this vertical slice is guaranteed
1189
- // to contain the monotonic segment, and our roots are returned in order by `solve_quadratic`.
1190
- // Adjust the winding as appropriate.
1191
- if direction != 0 {
1192
- let t_x = x0 + bx * t + ax * t * t;
1193
- if t_x > 0.0 {
1194
- winding += direction;
1191
+ // First subcurve is moving downward, include extrema point.
1192
+ if is_t1_valid && y0 < 0.0 && y_max >= 0.0 {
1193
+ let x = x0 + bx * t1 + ax * t1 * t1;
1194
+ if x > 0.0 {
1195
+ winding -= 1 ;
1195
1196
}
1196
1197
}
1197
1198
1198
- // Advance to next monotonic segment
1199
- y_start = y_end;
1200
- y_end = y2;
1199
+ // Second subcurve is moving upward, include extrema point.
1200
+ if is_t0_valid && y_max >= 0.0 && y2 < 0.0 {
1201
+ let x = x0 + bx * t0 + ax * t0 * t0;
1202
+ if x > 0.0 {
1203
+ winding += 1 ;
1204
+ }
1205
+ }
1201
1206
}
1202
1207
1203
1208
winding
@@ -1206,43 +1211,35 @@ fn winding_number_curve(
1206
1211
const COEFFICIENT_EPSILON : f64 = 0.0000001 ;
1207
1212
1208
1213
/// Returns the roots of the quadratic ax^2 + bx + c = 0.
1209
- /// The roots may not be unique.
1214
+ /// The roots may not be unique. NAN is returned for invalid roots. The first root will be where
1215
+ /// the curve is sloping upward, the second root will be where the curve is slopping downward.
1210
1216
/// Uses the "Citardauq" formula for numerical stability.
1211
- /// Originally from https://github.com/linebender/kurbo/blob/master/src/common.rs
1212
1217
/// See https://math.stackexchange.com/questions/866331
1213
- fn solve_quadratic ( a : f64 , b : f64 , c : f64 ) -> SmallVec < [ f64 ; 2 ] > {
1214
- let mut result = SmallVec :: new ( ) ;
1215
- let sc0 = c * a. recip ( ) ;
1216
- let sc1 = b * a. recip ( ) ;
1217
- if !sc0. is_finite ( ) || !sc1. is_finite ( ) {
1218
- // c2 is zero or very small, treat as linear eqn
1219
- let root = -c / b;
1220
- if root. is_finite ( ) {
1221
- result. push ( root) ;
1218
+ fn solve_quadratic ( a : f64 , b : f64 , c : f64 ) -> ( f64 , f64 ) {
1219
+ if a. abs ( ) <= COEFFICIENT_EPSILON {
1220
+ // Nearly linear, solve as linear equation.
1221
+ if b >= 0.0 {
1222
+ return ( f64:: NAN , -c / b) ;
1223
+ } else {
1224
+ return ( -c / b, f64:: NAN ) ;
1222
1225
}
1223
- return result;
1224
1226
}
1225
- let arg = sc1 * sc1 - 4.0 * sc0;
1226
- let root1 = if !arg. is_finite ( ) {
1227
- // Likely, calculation of sc1 * sc1 overflowed. Find one root
1228
- // using sc1 x + x² = 0, other root as sc0 / root1.
1229
- -sc1
1230
- } else {
1231
- if arg < 0.0 {
1232
- return result;
1233
- }
1234
- -0.5 * ( sc1 + arg. sqrt ( ) . copysign ( sc1) )
1235
- } ;
1236
- let root2 = sc0 / root1;
1237
- // Sort just to be friendly and make results deterministic.
1238
- if root2 > root1 {
1239
- result. push ( root1) ;
1240
- result. push ( root2) ;
1227
+ let mut disc = b * b - 4.0 * a * c;
1228
+ if disc < 0.0 {
1229
+ return ( f64:: NAN , f64:: NAN ) ;
1230
+ }
1231
+ disc = disc. sqrt ( ) ;
1232
+ // Order the roots so that the first root is where the curve slopes upward,
1233
+ // and the second root is where the root slopes downward.
1234
+ if b >= 0.0 {
1235
+ let root0 = ( -b - disc) / ( 2.0 * a) ;
1236
+ let root1 = c / ( a * root0) ;
1237
+ ( root0, root1)
1241
1238
} else {
1242
- result. push ( root2) ;
1243
- result. push ( root1) ;
1239
+ let root0 = ( -b + disc) / ( 2.0 * a) ;
1240
+ let root1 = c / ( a * root0) ;
1241
+ ( root1, root0)
1244
1242
}
1245
- result
1246
1243
}
1247
1244
1248
1245
/// Returns the roots of a cubic polynomial, ax^3 + bx^2 + cx + d = 0
@@ -1255,7 +1252,8 @@ fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> SmallVec<[f64; 3]> {
1255
1252
1256
1253
if a. abs ( ) <= COEFFICIENT_EPSILON {
1257
1254
// Fall back to quadratic formula.
1258
- roots. extend_from_slice ( & solve_quadratic ( b, c, d) ) ;
1255
+ let ( t0, t1) = solve_quadratic ( b, c, d) ;
1256
+ roots. extend_from_slice ( & [ t0, t1] ) ;
1259
1257
return roots;
1260
1258
}
1261
1259
0 commit comments