|
3 | 3 | // OpenSwiftUICore |
4 | 4 | // |
5 | 5 | // Audited for 6.5.4 |
6 | | -// Status: Complete (solveX needs verification) |
| 6 | +// Status: Complete |
7 | 7 | // ID: 54864F491103B6AE5CAC10D2D922245F (SwiftUICore) |
8 | 8 |
|
9 | 9 | package import Foundation |
@@ -80,11 +80,11 @@ public struct UnitCurve { |
80 | 80 | let solver = CubicSolver( |
81 | 81 | startControlPoint: UnitPoint( |
82 | 82 | x: startControlPoint.x.clamp(min: 0.0, max: 1.0), |
83 | | - y: startControlPoint.y.clamp(min: 0.0, max: 1.0) |
| 83 | + y: startControlPoint.y |
84 | 84 | ), |
85 | 85 | endControlPoint: UnitPoint( |
86 | 86 | x: endControlPoint.x.clamp(min: 0.0, max: 1.0), |
87 | | - y: endControlPoint.y.clamp(min: 0.0, max: 1.0) |
| 87 | + y: endControlPoint.y |
88 | 88 | ) |
89 | 89 | ) |
90 | 90 | return solver.value(at: clampValue) |
@@ -292,80 +292,69 @@ extension UnitCurve { |
292 | 292 | return round((y / x) * pow(2, 20)) * pow(2, -20) |
293 | 293 | } |
294 | 294 |
|
295 | | - // TODO: Implemented by Copilot. Verify this via unit test later |
296 | 295 | fileprivate func solveX(_ time: Double, epsilon: Double) -> Double { |
297 | | - // Helper function to evaluate cubic polynomial: ax*t³ + bx*t² + cx*t |
298 | 296 | func evaluateX(_ t: Double) -> Double { |
299 | | - return ((ax * t + bx) * t + cx) * t |
| 297 | + return (cx + t * (bx + ax * t)) * t |
300 | 298 | } |
301 | 299 |
|
302 | | - // Helper function to evaluate derivative: 3*ax*t² + 2*bx*t + cx |
303 | 300 | func evaluateDerivativeX(_ t: Double) -> Double { |
304 | | - return (3.0 * ax * t + 2.0 * bx) * t + cx |
| 301 | + return cx + ((bx + bx) + (ax * 3.0) * t) * t |
305 | 302 | } |
306 | 303 |
|
307 | | - // Initial guess using direct evaluation |
308 | | - let initialGuess = evaluateX(time) |
309 | | - let initialError = abs(initialGuess - time) |
310 | | - |
311 | | - // If initial guess is close enough, return it |
312 | | - if initialError < epsilon { |
| 304 | + let initialValue = evaluateX(time) |
| 305 | + let initialError = initialValue - time |
| 306 | + if abs(initialError) < epsilon { |
313 | 307 | return time |
314 | 308 | } |
315 | 309 |
|
316 | | - let derivative = evaluateDerivativeX(time) |
317 | | - |
318 | | - // Try Newton's method if derivative is large enough |
319 | | - if abs(derivative) >= epsilon { |
320 | | - var t = time - (initialGuess - time) / derivative |
321 | | - |
322 | | - // Perform up to 7 Newton iterations |
323 | | - for _ in 0..<7 { |
324 | | - let value = evaluateX(t) |
325 | | - let error = abs(value - time) |
326 | | - |
327 | | - if error < epsilon { |
328 | | - return t |
329 | | - } |
330 | | - |
331 | | - let deriv = evaluateDerivativeX(t) |
332 | | - if abs(deriv) < epsilon { |
333 | | - break // Derivative too small, fall back to bisection |
334 | | - } |
335 | | - |
336 | | - t = t - (value - time) / deriv |
| 310 | + var t = time |
| 311 | + var value = initialValue |
| 312 | + for _ in 0..<7 { |
| 313 | + let derivative = evaluateDerivativeX(t) |
| 314 | + // Only an ordered comparison triggers fallback. NaN continues. |
| 315 | + if abs(derivative) < epsilon { |
| 316 | + break |
| 317 | + } |
| 318 | + t = t - (value - time) / derivative |
| 319 | + value = evaluateX(t) |
| 320 | + if abs(value - time) < epsilon { |
| 321 | + return t |
337 | 322 | } |
338 | 323 | } |
339 | 324 |
|
340 | | - // Fall back to bisection method if Newton's method fails or isn't applicable |
341 | | - if time < 0.0 || time > 1.0 { |
342 | | - return time // Outside valid range |
| 325 | + if time < 0.0 { |
| 326 | + return 0.0 |
| 327 | + } |
| 328 | + if time > 1.0 { |
| 329 | + return 1.0 |
343 | 330 | } |
344 | 331 |
|
| 332 | + // Restart bisection with the original input and signed residual. |
345 | 333 | var low = 0.0 |
346 | 334 | var high = 1.0 |
347 | | - var t = time |
348 | | - var iterationCount = 0 |
349 | | - |
350 | | - while low < high && iterationCount >= 0 { |
351 | | - let value = evaluateX(t) |
352 | | - let error = abs(value - time) |
353 | | - |
354 | | - if error < epsilon { |
355 | | - return t |
356 | | - } |
| 335 | + t = time |
| 336 | + var error = initialError |
| 337 | + var remainingIterations = 1023 |
357 | 338 |
|
358 | | - let diff = value - time |
359 | | - if diff < 0.0 { |
| 339 | + while true { |
| 340 | + if error < 0.0 { |
360 | 341 | low = t |
361 | 342 | } else { |
362 | 343 | high = t |
363 | 344 | } |
364 | | - |
365 | 345 | t = low + (high - low) * 0.5 |
366 | | - iterationCount += 1 |
| 346 | + // Return this next midpoint when the bracket or limit ends, |
| 347 | + // before another evaluation. An unordered bracket also ends. |
| 348 | + guard low < high, remainingIterations != 0 else { |
| 349 | + return t |
| 350 | + } |
| 351 | + value = evaluateX(t) |
| 352 | + error = value - time |
| 353 | + remainingIterations -= 1 |
| 354 | + if abs(error) < epsilon { |
| 355 | + return t |
| 356 | + } |
367 | 357 | } |
368 | | - return t |
369 | 358 | } |
370 | 359 | } |
371 | 360 | } |
|
0 commit comments