Skip to content

stevebrun/DampingField.playground

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DampingField.playground

Question

I'm creating a custom field to slow down the motion of objects after a user performs a pan gesture. Using normal friction makes the objects feel too slippery, so I'm trying to apply spring physics in place of friction.

I have a function that seems to correctly calculate the behavior I'm looking for.

extension UIFieldBehavior {
    static func dampingField(_ constant: CGFloat) -> UIFieldBehavior {
        return UIFieldBehavior.field { field, position, velocity, mass, charge, time in
            let speed = sqrt(pow(velocity.dx, 2) + pow(velocity.dy, 2))
            let angle = acos(velocity.dx / speed)
            let force = -constant * speed
            guard angle.isNaN == false, force.isNaN == false
                else { return .zero }
            return  CGVector(dx: cos(angle) * force, dy: sin(angle) * force)
        }
    }
}

However, vertical motion isn't behaving as I expected. Any motion towards the reference view's top causes the object to accelerate faster.

I've been playing with trigonometry for a while, but I'm stumped.

Stack Overflow Question

Answer

The answer literally came to me in a dream, lol.

The unexpected behavior was that the force in the negative-y direction was negative when it should have been positive—causing an increase in the resulting velocity's absolute value.

Sure enough, adding a check to make sure that the force's y-component always had the opposite sign of the given velocity's y-component solved the problem.

var vector = CGVector(dx: cos(angle) * force, dy: sin(angle) * force)
if vector.dy.sign == velocity.dy.sign {
    vector.dy *= -1
}
return vector

Trying to think through why only the y-component was being wrongly signed, I noticed that the angle was calculated with respect to the x-axis.

let angle = acos(velocity.dx / speed)

I figured I'd try calculating the y-coordinate's force in terms of the angle with respect to the y-axis, and this too fixed the problem.

return CGVector(dx: cos(angle) * force, dy: sin(asin(velocity.dy / speed)) * force)

Thinking about it for a bit, I realized that, since asin and acos are the inverse of sin and cos respectively, the code could be reduced to remove the use of sin and cos entirely.

return CGVector(dx: velocity.dx / speed * force, dy: velocity.dy / speed * force)

Really, though, I didn't need to bring trigonometry into this at all, since operations on vectors' components are equivalent to operations on the vector itself. Now my force works as expected and is simpler to reason about.

extension UIFieldBehavior {
    static func dampingField(_ constant: CGFloat) -> UIFieldBehavior {
        return UIFieldBehavior.field { field, position, velocity, mass, charge, time in
            return  CGVector(dx: -constant * velocity.dx, dy: -constant * velocity.dy)
        }
    }
}

About

Unexpected Acceleration in Custom UIFieldBehavior

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages