123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- """A module for generating Bezier easing functions."""
- # These values are established by empiricism with tests (tradeoff: performance VS precision)
- NEWTON_ITERATIONS = 4
- NEWTON_MIN_SLOPE = 0.001
- SUBDIVISION_PRECISION = 0.0000001
- SUBDIVISION_MAX_ITERATIONS = 10
- kSplineTableSize = 11
- kSampleStepSize = 1.0 / (kSplineTableSize - 1.0)
- def A(aA1, aA2):
- """Calculate A.
- Args:
- aA1: The first value.
- aA2: The second value.
- Returns:
- The calculated value.
- """
- return 1.0 - 3.0 * aA2 + 3.0 * aA1
- def B(aA1, aA2):
- """Calculate B.
- Args:
- aA1: The first value.
- aA2: The second value.
- Returns:
- The calculated value.
- """
- return 3.0 * aA2 - 6.0 * aA1
- def C(aA1):
- """Calculate C.
- Args:
- aA1: The first value.
- Returns:
- The calculated value.
- """
- return 3.0 * aA1
- def calcBezier(aT, aA1, aA2):
- """Calculate Bezier.
- Args:
- aT: The time.
- aA1: The first value.
- aA2: The second value.
- Returns:
- x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
- """
- return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT
- def getSlope(aT, aA1, aA2):
- """Calculate slope.
- Args:
- aT: The time.
- aA1: The first value.
- aA2: The second value.
- Returns:
- dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
- """
- return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1)
- def binarySubdivide(aX, aA, aB, mX1, mX2):
- """Perform a binary subdivide.
- Args:
- aX: The x value.
- aA: The a value.
- aB: The b value.
- mX1: The x1 value.
- mX2: The x2 value.
- Returns:
- The t value.
- """
- current_x = aA
- current_t = 0
- i = 0
- while True:
- i += 1
- if i >= SUBDIVISION_MAX_ITERATIONS:
- break
- current_t = aA + (aB - aA) / 2.0
- current_x = calcBezier(current_t, mX1, mX2) - aX
- if current_x > 0.0:
- aB = current_t
- else:
- aA = current_t
- if abs(current_x) <= SUBDIVISION_PRECISION:
- break
- return current_t
- def newtonRaphsonIterate(aX, aGuessT, mX1, mX2):
- """Perform a Newton-Raphson iteration.
- Args:
- aX: The x value.
- aGuessT: The guess value.
- mX1: The x1 value.
- mX2: The x2 value.
- Returns:
- The t value.
- """
- for _ in range(NEWTON_ITERATIONS):
- current_slope = getSlope(aGuessT, mX1, mX2)
- if current_slope == 0.0:
- return aGuessT
- current_x = calcBezier(aGuessT, mX1, mX2) - aX
- aGuessT -= current_x / current_slope
- return aGuessT
- def LinearEasing(x):
- """Linear easing function.
- Args:
- x: The x value.
- Returns:
- The x value.
- """
- return x
- def bezier(mX1, mY1, mX2, mY2):
- """Generate a Bezier easing function.
- Args:
- mX1: The x1 value.
- mY1: The y1 value.
- mX2: The x2 value.
- mY2: The y2 value.
- Raises:
- ValueError: If the x values are not in the [0, 1] range.
- Returns:
- The Bezier easing function.
- """
- if not (0 <= mX1 <= 1 and 0 <= mX2 <= 1):
- raise ValueError("bezier x values must be in [0, 1] range")
- if mX1 == mY1 and mX2 == mY2:
- return LinearEasing
- # Precompute samples table
- sampleValues = [
- calcBezier(i * kSampleStepSize, mX1, mX2) for i in range(kSplineTableSize)
- ]
- def getTForX(aX):
- intervalStart = 0.0
- currentSample = 1
- lastSample = kSplineTableSize - 1
- while currentSample != lastSample and sampleValues[currentSample] <= aX:
- intervalStart += kSampleStepSize
- currentSample += 1
- currentSample -= 1
- # Interpolate to provide an initial guess for t
- dist = (aX - sampleValues[currentSample]) / (
- sampleValues[currentSample + 1] - sampleValues[currentSample]
- )
- guessForT = intervalStart + dist * kSampleStepSize
- initialSlope = getSlope(guessForT, mX1, mX2)
- if initialSlope >= NEWTON_MIN_SLOPE:
- return newtonRaphsonIterate(aX, guessForT, mX1, mX2)
- elif initialSlope == 0.0:
- return guessForT
- else:
- return binarySubdivide(
- aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2
- )
- def BezierEasing(x):
- if x == 0 or x == 1:
- return x
- return calcBezier(getTForX(x), mY1, mY2)
- return BezierEasing
|