1 /*
2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
18
19 package androidx.compose.ui.graphics
20
21 import androidx.annotation.RestrictTo
22 import androidx.collection.FloatFloatPair
23 import androidx.compose.ui.util.fastCbrt
24 import androidx.compose.ui.util.fastCoerceIn
25 import androidx.compose.ui.util.fastMaxOf
26 import androidx.compose.ui.util.fastMinOf
27 import androidx.compose.ui.util.lerp
28 import kotlin.math.PI
29 import kotlin.math.abs
30 import kotlin.math.acos
31 import kotlin.math.cos
32 import kotlin.math.max
33 import kotlin.math.min
34 import kotlin.math.sign
35 import kotlin.math.sqrt
36
37 private const val Tau = PI * 2.0
38 private const val Epsilon = 1e-7
39 // We use a fairly high epsilon here because it's post double->float conversion
40 // and because we use a fast approximation of cbrt(). The epsilon we use here is
41 // slightly larger than the max error of fastCbrt() in the -1f..1f range
42 // (8.3446500e-7f) but smaller than 1.0f.ulp * 10.
43 private const val FloatEpsilon = 1.05e-6f
44
45 /**
46 * Evaluate the specified [segment] at position [t] and returns the X coordinate of the segment's
47 * curve at that position.
48 */
evaluateXnull49 private fun evaluateX(segment: PathSegment, t: Float): Float {
50 val points = segment.points
51
52 return when (segment.type) {
53 PathSegment.Type.Move -> points[0]
54 PathSegment.Type.Line -> {
55 evaluateLine(points[0], points[2], t)
56 }
57 PathSegment.Type.Quadratic -> {
58 evaluateQuadratic(points[0], points[2], points[4], t)
59 }
60 PathSegment.Type.Cubic -> {
61 evaluateCubic(points[0], points[2], points[4], points[6], t)
62 }
63 // Conic (converted to Cubic), Close, Done
64 else -> Float.NaN
65 }
66 }
67
68 /**
69 * Evaluate the specified [segment] at position [t] and returns the Y coordinate of the segment's
70 * curve at that position.
71 */
72 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
evaluateYnull73 fun evaluateY(segment: PathSegment, t: Float): Float {
74 val points = segment.points
75
76 return when (segment.type) {
77 PathSegment.Type.Move -> points[1]
78 PathSegment.Type.Line -> {
79 evaluateLine(points[1], points[3], t)
80 }
81 PathSegment.Type.Quadratic -> {
82 evaluateQuadratic(points[1], points[3], points[5], t)
83 }
84 PathSegment.Type.Cubic -> {
85 evaluateCubic(points[1], points[3], points[5], points[7], t)
86 }
87 // Conic (converted to Cubic), Close, Done
88 else -> Float.NaN
89 }
90 }
91
evaluateLinenull92 private fun evaluateLine(p0y: Float, p1y: Float, t: Float) = (p1y - p0y) * t + p0y
93
94 private fun evaluateQuadratic(p0: Float, p1: Float, p2: Float, t: Float): Float {
95 val by = 2.0f * (p1 - p0)
96 val ay = p2 - 2.0f * p1 + p0
97 return (ay * t + by) * t + p0
98 }
99
evaluateCubicnull100 private fun evaluateCubic(p0: Float, p1: Float, p2: Float, p3: Float, t: Float): Float {
101 val a = p3 + 3.0f * (p1 - p2) - p0
102 val b = 3.0f * (p2 - 2.0f * p1 + p0)
103 val c = 3.0f * (p1 - p0)
104 return ((a * t + b) * t + c) * t + p0
105 }
106
107 /**
108 * Evaluates a cubic Bézier curve at position [t] along the curve. The curve is defined by the start
109 * point (0, 0), the end point (0, 0) and two control points of respective coordinates [p1] and
110 * [p2].
111 */
112 @Suppress("UnnecessaryVariable")
113 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
evaluateCubicnull114 fun evaluateCubic(p1: Float, p2: Float, t: Float): Float {
115 val a = 1.0f / 3.0f + (p1 - p2)
116 val b = (p2 - 2.0f * p1)
117 val c = p1
118 return 3.0f * ((a * t + b) * t + c) * t
119 }
120
121 /**
122 * Finds the first real root of the specified [segment]. If no root can be found, this method
123 * returns [Float.NaN].
124 */
125 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
findFirstRootnull126 fun findFirstRoot(segment: PathSegment, fraction: Float): Float {
127 val points = segment.points
128 return when (segment.type) {
129 PathSegment.Type.Move -> Float.NaN
130 PathSegment.Type.Line -> {
131 findFirstLineRoot(
132 points[0] - fraction,
133 points[2] - fraction,
134 )
135 }
136 PathSegment.Type.Quadratic ->
137 findFirstQuadraticRoot(points[0] - fraction, points[2] - fraction, points[4] - fraction)
138
139 // We convert all conics to cubics, won't happen
140 PathSegment.Type.Conic -> Float.NaN
141 PathSegment.Type.Cubic ->
142 findFirstCubicRoot(
143 points[0] - fraction,
144 points[2] - fraction,
145 points[4] - fraction,
146 points[6] - fraction
147 )
148 PathSegment.Type.Close -> Float.NaN
149 PathSegment.Type.Done -> Float.NaN
150 }
151 }
152
findFirstLineRootnull153 private inline fun findFirstLineRoot(p0: Float, p1: Float) =
154 clampValidRootInUnitRange(-p0 / (p1 - p0))
155
156 /**
157 * Finds the first real root of a quadratic Bézier curve:
158 * - [p0]: coordinate of the start point
159 * - [p1]: coordinate of the control point
160 * - [p2]: coordinate of the end point
161 *
162 * If no root can be found, this method returns [Float.NaN].
163 */
164 private fun findFirstQuadraticRoot(p0: Float, p1: Float, p2: Float): Float {
165 val a = p0.toDouble()
166 val b = p1.toDouble()
167 val c = p2.toDouble()
168 val d = a - 2.0 * b + c
169
170 if (d != 0.0) {
171 val v1 = -sqrt(b * b - a * c)
172 val v2 = -a + b
173
174 val root = clampValidRootInUnitRange((-(v1 + v2) / d).toFloat())
175 if (!root.isNaN()) return root
176
177 return clampValidRootInUnitRange(((v1 - v2) / d).toFloat())
178 } else if (b != c) {
179 return clampValidRootInUnitRange(((2.0 * b - c) / (2.0 * b - 2.0 * c)).toFloat())
180 }
181
182 return Float.NaN
183 }
184
185 /**
186 * Finds the first real root of a cubic Bézier curve:
187 * - [p0]: coordinate of the start point
188 * - [p1]: coordinate of the first control point
189 * - [p2]: coordinate of the second control point
190 * - [p3]: coordinate of the end point
191 *
192 * If no root can be found, this method returns [Float.NaN].
193 */
194 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
findFirstCubicRootnull195 fun findFirstCubicRoot(p0: Float, p1: Float, p2: Float, p3: Float): Float {
196 // This function implements Cardano's algorithm as described in "A Primer on Bézier Curves":
197 // https://pomax.github.io/bezierinfo/#yforx
198 //
199 // The math used to find the roots is explained in "Solving the Cubic Equation":
200 // http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm
201
202 var a = 3.0 * (p0 - 2.0 * p1 + p2)
203 var b = 3.0 * (p1 - p0)
204 var c = p0.toDouble()
205 val d = -p0 + 3.0 * (p1 - p2) + p3
206
207 // Not a cubic
208 if (d.closeTo(0.0)) {
209 // Not a quadratic
210 if (a.closeTo(0.0)) {
211 // No solutions
212 if (b.closeTo(0.0)) {
213 return Float.NaN
214 }
215 return clampValidRootInUnitRange((-c / b).toFloat())
216 } else {
217 val q = sqrt(b * b - 4.0 * a * c)
218 val a2 = 2.0 * a
219
220 val root = clampValidRootInUnitRange(((q - b) / a2).toFloat())
221 if (!root.isNaN()) return root
222
223 return clampValidRootInUnitRange(((-b - q) / a2).toFloat())
224 }
225 }
226
227 a /= d
228 b /= d
229 c /= d
230
231 val o3 = (3.0 * b - a * a) / 9.0
232 val q2 = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0
233 val discriminant = q2 * q2 + o3 * o3 * o3
234 val a3 = a / 3.0
235
236 if (discriminant < 0.0) {
237 val mp33 = -(o3 * o3 * o3)
238 val r = sqrt(mp33)
239 val t = -q2 / r
240 val cosPhi = t.fastCoerceIn(-1.0, 1.0)
241 val phi = acos(cosPhi)
242 val t1 = 2.0f * fastCbrt(r.toFloat())
243
244 var root = clampValidRootInUnitRange((t1 * cos(phi / 3.0) - a3).toFloat())
245 if (!root.isNaN()) return root
246
247 root = clampValidRootInUnitRange((t1 * cos((phi + Tau) / 3.0) - a3).toFloat())
248 if (!root.isNaN()) return root
249
250 return clampValidRootInUnitRange((t1 * cos((phi + 2.0 * Tau) / 3.0) - a3).toFloat())
251 } else if (discriminant == 0.0) { // TODO: closeTo(0.0)?
252 val u1 = -fastCbrt(q2.toFloat())
253
254 val root = clampValidRootInUnitRange(2.0f * u1 - a3.toFloat())
255 if (!root.isNaN()) return root
256
257 return clampValidRootInUnitRange(-u1 - a3.toFloat())
258 }
259
260 val sd = sqrt(discriminant)
261 val u1 = fastCbrt((-q2 + sd).toFloat())
262 val v1 = fastCbrt((q2 + sd).toFloat())
263
264 return clampValidRootInUnitRange((u1 - v1 - a3).toFloat())
265 }
266
267 /**
268 * Finds the real root of a line defined by the X coordinates of its start ([p0]) and end ([p1])
269 * points. The root, if any, is written in the [roots] array at [index]. Returns 1 if a root was
270 * found, 0 otherwise.
271 */
findLineRootnull272 private inline fun findLineRoot(p0: Float, p1: Float, roots: FloatArray, index: Int = 0) =
273 writeValidRootInUnitRange(-p0 / (p1 - p0), roots, index)
274
275 /**
276 * Finds the real roots of a quadratic Bézier curve. To find the roots, only the X coordinates of
277 * the four points are required:
278 * - [p0]: x coordinate of the start point
279 * - [p1]: x coordinate of the control point
280 * - [p2]: x coordinate of the end point
281 *
282 * Any root found is written in the [roots] array, starting at [index]. The function returns the
283 * number of roots found and written to the array.
284 */
285 private fun findQuadraticRoots(
286 p0: Float,
287 p1: Float,
288 p2: Float,
289 roots: FloatArray,
290 index: Int = 0
291 ): Int {
292 val a = p0.toDouble()
293 val b = p1.toDouble()
294 val c = p2.toDouble()
295 val d = a - 2.0 * b + c
296
297 var rootCount = 0
298
299 if (d != 0.0) {
300 val v1 = -sqrt(b * b - a * c)
301 val v2 = -a + b
302
303 rootCount += writeValidRootInUnitRange((-(v1 + v2) / d).toFloat(), roots, index)
304 rootCount += writeValidRootInUnitRange(((v1 - v2) / d).toFloat(), roots, index + rootCount)
305
306 // Returns the roots sorted
307 if (rootCount > 1) {
308 val s = roots[index]
309 val t = roots[index + 1]
310 if (s > t) {
311 roots[index] = t
312 roots[index + 1] = s
313 } else if (s == t) {
314 // Don't report identical roots
315 rootCount--
316 }
317 }
318 } else if (b != c) {
319 rootCount +=
320 writeValidRootInUnitRange(((2.0 * b - c) / (2.0 * b - 2.0 * c)).toFloat(), roots, index)
321 }
322
323 return rootCount
324 }
325
326 /**
327 * Finds the roots of the derivative of the curve described by [segment]. The roots, if any, are
328 * written in the [roots] array starting at [index]. The function returns the number of roots founds
329 * and written into the array. The [roots] array must be able to hold at least 5 floats starting at
330 * [index].
331 */
findDerivativeRootsnull332 private fun findDerivativeRoots(
333 segment: PathSegment,
334 horizontal: Boolean,
335 roots: FloatArray,
336 index: Int
337 ): Int {
338 val offset = if (horizontal) 0 else 1
339 val points = segment.points
340 return when (segment.type) {
341 PathSegment.Type.Quadratic -> {
342 // Line derivative of a quadratic function
343 // We do the computation inline to avoid using arrays of other data
344 // structures to return the result
345 val d0 = 2 * (points[offset + 2] - points[offset + 0])
346 val d1 = 2 * (points[offset + 4] - points[offset + 2])
347 findLineRoot(d0, d1, roots, index)
348 }
349 PathSegment.Type.Cubic -> {
350 // Quadratic derivative of a cubic function
351 // We do the computation inline to avoid using arrays of other data
352 // structures to return the result
353 val d0 = 3.0f * (points[offset + 2] - points[offset + 0])
354 val d1 = 3.0f * (points[offset + 4] - points[offset + 2])
355 val d2 = 3.0f * (points[offset + 6] - points[offset + 4])
356 val count = findQuadraticRoots(d0, d1, d2, roots, index)
357
358 // Compute the second derivative as a line
359 val dd0 = 2.0f * (d1 - d0)
360 val dd1 = 2.0f * (d2 - d1)
361 // Return the sum of the roots count
362 count + findLineRoot(dd0, dd1, roots, index + count)
363 }
364 else -> 0
365 }
366 }
367
368 /**
369 * Computes the horizontal bounds of the specified [segment] and returns a pair of floats containing
370 * the lowest bound as the first value, and the highest bound as the second value.
371 *
372 * The [roots] array is used as a scratch array and must be able to hold at least 5 floats.
373 */
374 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
computeHorizontalBoundsnull375 fun computeHorizontalBounds(
376 segment: PathSegment,
377 roots: FloatArray,
378 index: Int = 0
379 ): FloatFloatPair {
380 val count = findDerivativeRoots(segment, true, roots, index)
381 var minX = min(segment.startX, segment.endX)
382 var maxX = max(segment.startX, segment.endX)
383
384 for (i in 0 until count) {
385 val t = roots[i]
386 val x = evaluateX(segment, t)
387 minX = min(minX, x)
388 maxX = max(maxX, x)
389 }
390
391 return FloatFloatPair(minX, maxX)
392 }
393
394 /**
395 * Computes the vertical bounds of the specified [segment] and returns a pair of floats containing
396 * the lowest bound as the first value, and the highest bound as the second value.
397 *
398 * The [roots] array is used as a scratch array and must be able to hold at least 5 floats.
399 */
computeVerticalBoundsnull400 internal fun computeVerticalBounds(
401 segment: PathSegment,
402 roots: FloatArray,
403 index: Int = 0
404 ): FloatFloatPair {
405 val count = findDerivativeRoots(segment, false, roots, index)
406 var minY = min(segment.startY, segment.endY)
407 var maxY = max(segment.startY, segment.endY)
408
409 for (i in 0 until count) {
410 val t = roots[i]
411 val x = evaluateY(segment, t)
412 minY = min(minY, x)
413 maxY = max(maxY, x)
414 }
415
416 return FloatFloatPair(minY, maxY)
417 }
418
419 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
computeCubicVerticalBoundsnull420 fun computeCubicVerticalBounds(
421 p0y: Float,
422 p1y: Float,
423 p2y: Float,
424 p3y: Float,
425 roots: FloatArray,
426 index: Int = 0
427 ): FloatFloatPair {
428 // Quadratic derivative of a cubic function
429 // We do the computation inline to avoid using arrays of other data
430 // structures to return the result
431 val d0 = 3.0f * (p1y - p0y)
432 val d1 = 3.0f * (p2y - p1y)
433 val d2 = 3.0f * (p3y - p2y)
434 var count = findQuadraticRoots(d0, d1, d2, roots, index)
435
436 // Compute the second derivative as a line
437 val dd0 = 2.0f * (d1 - d0)
438 val dd1 = 2.0f * (d2 - d1)
439 count += findLineRoot(dd0, dd1, roots, index + count)
440
441 var minY = min(p0y, p3y)
442 var maxY = max(p0y, p3y)
443
444 for (i in 0 until count) {
445 val t = roots[i]
446 val y = evaluateCubic(p0y, p1y, p2y, p3y, t)
447 minY = min(minY, y)
448 maxY = max(maxY, y)
449 }
450
451 return FloatFloatPair(minY, maxY)
452 }
453
closeTonull454 internal inline fun Double.closeTo(b: Double) = abs(this - b) < Epsilon
455
456 internal inline fun Float.closeTo(b: Float) = abs(this - b) < FloatEpsilon
457
458 /**
459 * Returns [r] if it's in the [0..1] range, and [Float.NaN] otherwise. To account for numerical
460 * imprecision in computations, values in the [-FloatEpsilon..1+FloatEpsilon] range are considered
461 * to be in the [0..1] range and clamped appropriately.
462 */
463 private inline fun clampValidRootInUnitRange(r: Float): Float {
464 // The code below is a branchless version of:
465 // if (r < 0.0f) {
466 // if (r >= -FloatEpsilon) 0.0f else Float.NaN
467 // } else if (r > 1.0f) {
468 // if (r <= 1.0f + FloatEpsilon) 1.0f else Float.NaN
469 // } else {
470 // r
471 // }
472 val s = r.fastCoerceIn(0f, 1f)
473 return if (abs(s - r) > FloatEpsilon) Float.NaN else s
474 }
475
476 /**
477 * Writes [r] in the [roots] array at [index], if it's in the [0..1] range. To account for numerical
478 * imprecision in computations, values in the [-FloatEpsilon..1+FloatEpsilon] range are considered
479 * to be in the [0..1] range and clamped appropriately. Returns 0 if no value was written, 1
480 * otherwise.
481 */
writeValidRootInUnitRangenull482 private fun writeValidRootInUnitRange(r: Float, roots: FloatArray, index: Int): Int {
483 val v = clampValidRootInUnitRange(r)
484 roots[index] = v
485 return if (v.isNaN()) 0 else 1
486 }
487
488 /**
489 * Computes the winding value for a position [x]/[y] and the line defined by the [points] array. The
490 * array must contain at least 4 floats defining the start and end points of the line as pairs of
491 * x/y coordinates.
492 */
lineWindingnull493 internal fun lineWinding(points: FloatArray, x: Float, y: Float): Int {
494 if (points.size < 4) return 0
495
496 val x0 = points[0]
497 var y0 = points[1]
498 val yo = y0
499 val x1 = points[2]
500 var y1 = points[3]
501
502 // Compute dy before we swap
503 val dy = y1 - y0
504 var direction = 1
505
506 if (y0 > y1) {
507 y0 = y1
508 y1 = yo
509 direction = -1
510 }
511
512 // We exclude the end point
513 if (y < y0 || y >= y1) {
514 return 0
515 }
516
517 // TODO: check if on the curve
518
519 val crossProduct = (x1 - x0) * (y - yo) - dy * (x - x0)
520 // The point is on the line
521 if (crossProduct == 0.0f) {
522 // TODO: check if we are on the line but exclude x1 and y1
523 direction = 0
524 } else if (crossProduct.sign.toInt() == direction) {
525 direction = 0
526 }
527
528 return direction
529 }
530
531 /**
532 * Returns whether the quadratic Bézier curve defined the start, control, and points [y0], [y1], and
533 * [y2] is monotonic on the Y axis.
534 */
isQuadraticMonotonicnull535 private fun isQuadraticMonotonic(y0: Float, y1: Float, y2: Float): Boolean =
536 (y0 - y1).sign + (y1 - y2).sign != 0.0f
537
538 /**
539 * Computes the winding value for a position [x]/[y] and the quadratic Bézier curve defined by the
540 * [points] array. The array must contain at least 6 floats defining the start, control, and end
541 * points of the curve as pairs of x/y coordinates.
542 *
543 * The [tmpQuadratics] array is a scratch array used to hold temporary values and must contain at
544 * least 10 floats. Its content can be ignored after calling this function.
545 *
546 * The [tmpRoots] array is a scratch array that must contain at least 2 values. It is used to hold
547 * temporary values and its content can be ignored after calling this function.
548 */
549 internal fun quadraticWinding(
550 points: FloatArray,
551 x: Float,
552 y: Float,
553 tmpQuadratics: FloatArray,
554 tmpRoots: FloatArray
555 ): Int {
556 val y0 = points[1]
557 val y1 = points[3]
558 val y2 = points[5]
559
560 if (isQuadraticMonotonic(y0, y1, y2)) {
561 return monotonicQuadraticWinding(points, 0, x, y, tmpRoots)
562 }
563
564 val rootCount = quadraticToMonotonicQuadratics(points, tmpQuadratics)
565
566 var winding = monotonicQuadraticWinding(tmpQuadratics, 0, x, y, tmpRoots)
567 if (rootCount > 0) {
568 winding += monotonicQuadraticWinding(tmpQuadratics, 4, x, y, tmpRoots)
569 }
570 return winding
571 }
572
573 /**
574 * Computes the winding value of a _monotonic_ quadratic Bézier curve for the given [x] and [y]
575 * coordinates. The curve is defined as 6 floats in the [points] array corresponding to the start,
576 * control, and end points. The floats are stored at position [offset] in the array, meaning the
577 * array must hold at least [offset] + 6 values.
578 *
579 * The [tmpRoots] array is a scratch array that must contain at least 2 values. It is used to hold
580 * temporary values and its content can be ignored after calling this function.
581 */
monotonicQuadraticWindingnull582 private fun monotonicQuadraticWinding(
583 points: FloatArray,
584 offset: Int,
585 x: Float,
586 y: Float,
587 tmpRoots: FloatArray
588 ): Int {
589 var y0 = points[offset + 1]
590 var y2 = points[offset + 5]
591
592 var direction = 1
593 if (y0 > y2) {
594 val swap = y2
595 y2 = y0
596 y0 = swap
597 direction = -1
598 }
599
600 // Exclude the end point
601 if (y < y0 || y >= y2) return 0
602
603 // TODO: check if on the curve
604
605 y0 = points[offset + 1]
606 val y1 = points[offset + 3]
607 y2 = points[offset + 5]
608
609 val rootCount = findQuadraticRoots(y0 - 2.0f * y1 + y2, 2.0f * (y1 - y0), y0 - y, tmpRoots)
610
611 val xt =
612 if (rootCount == 0) {
613 points[(1 - direction) * 2]
614 } else {
615 evaluateQuadratic(points[0], points[2], points[4], tmpRoots[0])
616 }
617
618 if (xt.closeTo(x)) {
619 if (x != points[4] || y != y2) {
620 // TODO: on the curve
621 return 0
622 }
623 }
624
625 return if (xt < x) direction else 0
626 }
627
628 /**
629 * Splits the specified [quadratic] Bézier curve into 1 or 2 monotonic quadratic Bézier curves. The
630 * results are stored in the [dst] array. Both the input [quadratic] and the output [dst] arrays
631 * store the curves as 3 pairs of floats defined by the start, control, and end points. In the [dst]
632 * array, successive curves share a point: the end point of the first curve is the start point of
633 * the second curve. As a result this function will output at most 10 values in the [dst] array (6
634 * floats per curve, minus 2 for a shared point).
635 *
636 * The function returns the number of splits: if 0 is returned, the [dst] array contains a single
637 * quadratic curve, if 1 is returned, the array contains 2 curves with a shared point.
638 */
quadraticToMonotonicQuadraticsnull639 private fun quadraticToMonotonicQuadratics(quadratic: FloatArray, dst: FloatArray): Int {
640 if (quadratic.size < 6) return 0
641 if (dst.size < 6) return 0
642
643 val y0 = quadratic[1]
644 var y1 = quadratic[3]
645 val y2 = quadratic[5]
646
647 if (!isQuadraticMonotonic(y0, y1, y2)) {
648 val t = unitDivide(y0 - y1, y0 - y1 - y1 + y2)
649 if (!t.isNaN()) {
650 splitQuadraticAt(quadratic, dst, t)
651 return 1
652 }
653 // force the curve to be monotonic since the division above failed
654 y1 = if (abs(y0 - y1) < abs(y1 - y2)) y0 else y2
655 }
656
657 quadratic.copyInto(dst, 0, 0, 6)
658 dst[3] = y1
659
660 return 0
661 }
662
663 /**
664 * Splits the specified [src] quadratic Bézier curve into two quadratic Bézier curves at position
665 * [t] (in the range 0..1), and stores the results in [dst]. The [dst] array must hold at least 10
666 * floats. See [quadraticToMonotonicQuadratics] for more details.
667 */
splitQuadraticAtnull668 private fun splitQuadraticAt(src: FloatArray, dst: FloatArray, t: Float) {
669 if (src.size < 6) return
670 if (dst.size < 10) return
671
672 val p0x = src[0]
673 val p0y = src[1]
674 val p1x = src[2]
675 val p1y = src[3]
676 val p2x = src[4]
677 val p2y = src[5]
678
679 val abx = lerp(p0x, p1x, t)
680 val aby = lerp(p0y, p1y, t)
681
682 dst[0] = p0x
683 dst[1] = p0y
684 dst[2] = abx
685 dst[3] = aby
686
687 val bcx = lerp(p1x, p2x, t)
688 val bcy = lerp(p1y, p2y, t)
689
690 val abcx = lerp(abx, bcx, t)
691 val abcy = lerp(aby, bcy, t)
692
693 dst[4] = abcx
694 dst[5] = abcy
695 dst[6] = bcx
696 dst[7] = bcy
697 dst[8] = p2x
698 dst[9] = p2y
699 }
700
701 /**
702 * Performs the division [x]/[y] and returns the result. If the division is invalid, for instance if
703 * it would leads to [Float.POSITIVE_INFINITY] or if it underflows, this function returns
704 * [Float.NaN].
705 */
unitDividenull706 private fun unitDivide(x: Float, y: Float): Float {
707 var n = x
708 var d = y
709
710 if (n < 0) {
711 n = -n
712 d = -d
713 }
714
715 if (d == 0.0f || n == 0.0f || n >= d) {
716 return Float.NaN
717 }
718
719 val r = n / d
720 if (r == 0.0f) {
721 return Float.NaN
722 }
723
724 return r
725 }
726
727 /**
728 * Computes the winding value for a position [x]/[y] and the cubic Bézier curve defined by the
729 * [points] array. The array must contain at least 8 floats defining the start, 2 control, and end
730 * points of the curve as pairs of x/y coordinates.
731 *
732 * The [tmpCubics] array is a scratch array used to hold temporary values and must contain at least
733 * 20 floats. Its content can be ignored after calling this function.
734 *
735 * The [tmpRoots] array is a scratch array that must contain at least 2 values. It is used to hold
736 * temporary values and its content can be ignored after calling this function.
737 */
cubicWindingnull738 internal fun cubicWinding(
739 points: FloatArray,
740 x: Float,
741 y: Float,
742 tmpCubics: FloatArray,
743 tmpRoots: FloatArray
744 ): Int {
745 val splits = cubicToMonotonicCubics(points, tmpCubics, tmpRoots)
746
747 var winding = 0
748 for (i in 0..splits) {
749 winding += monotonicCubicWinding(tmpCubics, i * 3 * 2, x, y)
750 }
751 return winding
752 }
753
754 /**
755 * Computes the winding value for a position [x]/[y] and the cubic Bézier curve defined by the
756 * [points] array, starting at the specified [offset]. The array must contain at least 10 floats
757 * after [offset] defining the start, control, and end points of the curve as pairs of x/y
758 * coordinates.
759 */
monotonicCubicWindingnull760 private fun monotonicCubicWinding(points: FloatArray, offset: Int, x: Float, y: Float): Int {
761 var y0 = points[offset + 1]
762 var y3 = points[offset + 7]
763
764 var direction = 1
765 if (y0 > y3) {
766 val swap = y3
767 y3 = y0
768 y0 = swap
769 direction = -1
770 }
771
772 // Exclude the end point
773 if (y < y0 || y >= y3) return 0
774
775 // TODO: check if on the curve
776
777 val x0 = points[offset + 0]
778 val x1 = points[offset + 2]
779 val x2 = points[offset + 4]
780 val x3 = points[offset + 6]
781
782 // Reject if outside of the bounds
783 val min = fastMinOf(x0, x1, x2, x3)
784 if (x < min) return 0
785
786 val max = fastMaxOf(x0, x1, x2, x3)
787 if (x > max) return direction
788
789 // Re-fetch y0 and y3 since we may have swapped them
790 y0 = points[offset + 1]
791 val y1 = points[offset + 3]
792 val y2 = points[offset + 5]
793 y3 = points[offset + 7]
794
795 val root =
796 findFirstCubicRoot(
797 y0 - y,
798 y1 - y,
799 y2 - y,
800 y3 - y,
801 )
802 if (root.isNaN()) return 0
803
804 val xt = evaluateCubic(x0, x1, x2, x3, root)
805 if (xt.closeTo(x)) {
806 if (x != x3 || y != y3) {
807 // TODO: on the curve
808 return 0
809 }
810 }
811
812 return if (xt < x) direction else 0
813 }
814
815 /**
816 * Splits the specified [cubic] Bézier curve into 1, 2, or 3 monotonic cubic Bézier curves. The
817 * results are stored in the [dst] array. Both the input [cubic] and the output [dst] arrays store
818 * the curves as 4 pairs of floats defined by the start, 2 control, and end points. In the [dst]
819 * array, successive curves share a point: the end point of the first curve is the start point of
820 * the second curve. As a result this function will output at most 20 values in the [dst] array (8
821 * floats per curve, minus 2 for each shared point).
822 *
823 * The function returns the number of splits: if 0 is returned, the [dst] array contains a single
824 * cubic curve, if 1 is returned, the array contains 2 curves with a shared point, and if 2 is
825 * returned, the array contains 3 curves with 2 shared points.
826 *
827 * The [tmpRoot] array is a scratch array that must contain at least 2 values. It is used to hold
828 * temporary values and its content can be ignored after calling this function.
829 */
cubicToMonotonicCubicsnull830 private fun cubicToMonotonicCubics(cubic: FloatArray, dst: FloatArray, tmpRoot: FloatArray): Int {
831 val rootCount = findCubicExtremaY(cubic, tmpRoot)
832
833 // Split the curve at the extrema
834 if (rootCount == 0) {
835 if (dst.size < 8) return 0
836 // The cubic segment is already monotonic, copy it as-is
837 cubic.copyInto(dst, 0, 0, 8)
838 } else {
839 var lastT = 0.0f
840 var dstOffset = 0
841 var src = cubic
842
843 for (i in 0 until rootCount) {
844 var t = tmpRoot[i]
845 t = ((t - lastT) / (1.0f - lastT)).fastCoerceIn(0.0f, 1.0f)
846 lastT = t
847 splitCubicAt(src, dstOffset, dst, dstOffset, t)
848 src = dst
849 dstOffset += 6
850 }
851 }
852
853 // NOTE: Should we flatten the extrema?
854
855 return rootCount
856 }
857
858 /**
859 * Finds the roots of the cubic function which coincide with the specified [cubic] Bézier curve's
860 * extrema on the Y axis. The roots are written in the specified [dstRoots] array which must hold at
861 * least 2 floats. This function returns the number of roots found: 0, 1, or 2.
862 */
findCubicExtremaYnull863 private fun findCubicExtremaY(cubic: FloatArray, dstRoots: FloatArray): Int {
864 val a = cubic[1]
865 val b = cubic[3]
866 val c = cubic[5]
867 val d = cubic[7]
868
869 val A = d - a + 3.0f * (b - c)
870 val B = 2.0f * (a - b - b - c)
871 val C = b - a
872
873 return findQuadraticRoots(A, B, C, dstRoots, 0)
874 }
875
876 /**
877 * Splits the cubic Bézier curve, specified by 4 pairs of floats (8 values) in the [src] array
878 * starting at the index [srcOffset], at position [t] (in the 0..1 range). The results are written
879 * in the [dst] array starting at index [dstOffset]. This function always outputs 2 curves sharing a
880 * point in the [dst] array, for a total of 14 float values: 8 for the first curve, 7 for the second
881 * curve (the end point of the first curve is shared as the start point of the second curve).
882 */
splitCubicAtnull883 private fun splitCubicAt(
884 src: FloatArray,
885 srcOffset: Int,
886 dst: FloatArray,
887 dstOffset: Int,
888 t: Float
889 ) {
890 if (src.size < srcOffset + 8) return
891 if (dst.size < dstOffset + 14) return
892
893 if (t >= 1.0f) {
894 src.copyInto(dst, dstOffset, srcOffset, 8)
895 val x = src[srcOffset + 6]
896 val y = src[srcOffset + 7]
897 dst[dstOffset + 8] = x
898 dst[dstOffset + 9] = y
899 dst[dstOffset + 10] = x
900 dst[dstOffset + 11] = y
901 dst[dstOffset + 12] = x
902 dst[dstOffset + 13] = y
903 return
904 }
905
906 val p0x = src[srcOffset + 0]
907 val p0y = src[srcOffset + 1]
908
909 dst[dstOffset + 0] = p0x
910 dst[dstOffset + 1] = p0y
911
912 val p1x = src[srcOffset + 2]
913 val p1y = src[srcOffset + 3]
914
915 val abx = lerp(p0x, p1x, t)
916 val aby = lerp(p0y, p1y, t)
917
918 dst[dstOffset + 2] = abx
919 dst[dstOffset + 3] = aby
920
921 val p2x = src[srcOffset + 4]
922 val p2y = src[srcOffset + 5]
923
924 val bcx = lerp(p1x, p2x, t)
925 val bcy = lerp(p1y, p2y, t)
926 val abcx = lerp(abx, bcx, t)
927 val abcy = lerp(aby, bcy, t)
928
929 dst[dstOffset + 4] = abcx
930 dst[dstOffset + 5] = abcy
931
932 val p3x = src[srcOffset + 6]
933 val p3y = src[srcOffset + 7]
934
935 val cdx = lerp(p2x, p3x, t)
936 val cdy = lerp(p2y, p3y, t)
937 val bcdx = lerp(bcx, cdx, t)
938 val bcdy = lerp(bcy, cdy, t)
939 val abcdx = lerp(abcx, bcdx, t)
940 val abcdy = lerp(abcy, bcdy, t)
941
942 dst[dstOffset + 6] = abcdx
943 dst[dstOffset + 7] = abcdy
944
945 dst[dstOffset + 8] = bcdx
946 dst[dstOffset + 9] = bcdy
947
948 dst[dstOffset + 10] = cdx
949 dst[dstOffset + 11] = cdy
950
951 dst[dstOffset + 12] = p3x
952 dst[dstOffset + 13] = p3y
953 }
954
955 /**
956 * Returns the signed area of the specified cubic Bézier curve.
957 *
958 * @param x0 The x coordinate of the curve's start point
959 * @param y0 The y coordinate of the curve's start point
960 * @param x1 The x coordinate of the curve's first control point
961 * @param y1 The y coordinate of the curve's first control point
962 * @param x2 The x coordinate of the curve's second control point
963 * @param y2 The y coordinate of the curve's second control point
964 * @param x3 The x coordinate of the curve's end point
965 * @param y3 The y coordinate of the curve's end point
966 */
cubicAreanull967 internal fun cubicArea(
968 x0: Float,
969 y0: Float,
970 x1: Float,
971 y1: Float,
972 x2: Float,
973 y2: Float,
974 x3: Float,
975 y3: Float
976 ): Float {
977 // See "Computing the area and winding number for a Bézier curve", Jackowski 2012
978 // https://tug.org/TUGboat/tb33-1/tb103jackowski.pdf
979 return ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2) + y1 * (x0 - x2) - x1 * (y0 - y2) +
980 y3 * (x2 + x0 / 3.0f) - x3 * (y2 + y0 / 3.0f)) * 3.0f / 20.0f
981 }
982
983 private inline val PathSegment.startX: Float
984 get() = points[0]
985
986 private val PathSegment.endX: Float
987 get() =
988 points[
989 when (type) {
990 PathSegment.Type.Line -> 2
991 PathSegment.Type.Quadratic -> 4
992 PathSegment.Type.Conic -> 4
993 PathSegment.Type.Cubic -> 6
994 else -> 0
995 }]
996
997 private inline val PathSegment.startY: Float
998 get() = points[1]
999
1000 private val PathSegment.endY: Float
1001 get() =
1002 points[
1003 when (type) {
1004 PathSegment.Type.Line -> 3
1005 PathSegment.Type.Quadratic -> 5
1006 PathSegment.Type.Conic -> 5
1007 PathSegment.Type.Cubic -> 7
1008 else -> 0
1009 }]
1010