1 /*
2 * Copyright (C) 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 package com.android.mechanics.spring
18
19 import androidx.compose.ui.util.fastCoerceIn
20 import androidx.compose.ui.util.lerp
21 import androidx.compose.ui.util.packFloats
22 import androidx.compose.ui.util.unpackFloat1
23 import androidx.compose.ui.util.unpackFloat2
24 import kotlin.math.pow
25
26 /**
27 * Describes the parameters of a spring.
28 *
29 * Note: This is conceptually compatible with the Compose [SpringSpec]. In contrast to the compose
30 * implementation, these [SpringParameters] are intended to be continuously updated.
31 *
32 * @see SpringParameters function to create this value.
33 */
34 @JvmInline
35 value class SpringParameters(val packedValue: Long) {
36 val stiffness: Float
37 get() = unpackFloat1(packedValue)
38
39 val dampingRatio: Float
40 get() = unpackFloat2(packedValue)
41
42 /** Whether the spring is expected to immediately end movement. */
43 val isSnapSpring: Boolean
44 get() = stiffness >= snapStiffness && dampingRatio == snapDamping
45
toStringnull46 override fun toString(): String {
47 return "MechanicsSpringSpec(stiffness=$stiffness, dampingRatio=$dampingRatio)"
48 }
49
50 companion object {
51 private val snapStiffness = 100_000f
52 private val snapDamping = 1f
53
54 /** A spring so stiff it completes the motion almost immediately. */
55 val Snap = SpringParameters(snapStiffness, snapDamping)
56 }
57 }
58
59 /** Creates a [SpringParameters] with the given [stiffness] and [dampingRatio]. */
SpringParametersnull60 fun SpringParameters(stiffness: Float, dampingRatio: Float): SpringParameters {
61 require(stiffness > 0) { "Spring stiffness constant must be positive." }
62 require(dampingRatio >= 0) { "Spring damping constant must be positive." }
63 return SpringParameters(packFloats(stiffness, dampingRatio))
64 }
65
66 /**
67 * Return interpolated [SpringParameters], based on the [fraction] between [start] and [stop].
68 *
69 * The [SpringParameters.dampingRatio] is interpolated linearly, the [SpringParameters.stiffness] is
70 * interpolated logarithmically.
71 *
72 * The [fraction] is clamped to a `0..1` range.
73 */
lerpnull74 fun lerp(start: SpringParameters, stop: SpringParameters, fraction: Float): SpringParameters {
75 val f = fraction.fastCoerceIn(0f, 1f)
76 val stiffness = start.stiffness.pow(1 - f) * stop.stiffness.pow(f)
77 val dampingRatio = lerp(start.dampingRatio, stop.dampingRatio, f)
78 return SpringParameters(packFloats(stiffness, dampingRatio))
79 }
80