SmoothDamp problem?

Hi All
I am using Mathf.SmoothDamp to control a camera via mouse movement, everything works great and the camera moves around nicely and if I let go of the camera control key the camera comes to a smooth stop, but only if the camera was moving in a positive direction when the key was released, if it was moving in a negative direction, ie currentVelocity param of SmootDamp is < 0.0f then the camera just stops suddenly. Am I using SmoothDamp incorrectly?

x = Mathf.SmoothDampAngle(x, nx, ref vx, delay);

In the line above if vx is +ve when the value of nx is not updated the x value will smoothly move towards nx as expected, if the value of vx is -ve when then the value of x will snap instantly to the value in nx.

Any ideas?
Chris

Could you post the rest of the code for this? I understand your troubles, but need to be able to test it.

Thanks for the interest :slight_smile: A very simple class that shows the behaviour is below, put it on a camera and when you hold the left mouse button and drag you will see if you drag to the right the camera does a nice damp move but drag to the left and it stops suddenly.

public class MyCamera : MonoBehaviour
{
    float nx = 0.0f;
    float ny = 0.0f;
    float x = 0.0f;
    float y = 0.0f;
    float vx = 0.0f;
    float vy = 0.0f;

    void LateUpdate()
    {
        if ( Input.GetMouseButton(0) )
        {
            float mx = Input.GetAxis("Mouse X");
            float my = Input.GetAxis("Mouse Y");

            nx = x - (my * 50.0f);
            ny = y + (mx * 50.0f);
            x = Mathf.SmoothDampAngle(x, nx, ref vx, 1.0f);
            y = Mathf.SmoothDampAngle(y, ny, ref vy, 1.0f);

            transform.rotation = Quaternion.Euler(x, y, 0.0f);
        }
    }
}

I am sure this is not what you want. The best I can see is that you are trying to mimic a Mouse Orbit and damping it.

To get SmoothDampAngle to work at all, I had to revert back to the documentation:

using UnityEngine;
using System.Collections;

public class MyCamera : MonoBehaviour
{
	public Transform target;
	public float smooth = 0.3F;
	public float distance = 5.0F;
	private float xVelocity = 0.0F;
	private float yVelocity = 0.0F;
	
	void Start(){
		Vector3 position = target.position;
		position += Quaternion.Euler(0, 0, 0) * new Vector3(0, 0, -distance);
		transform.position = position;
	}
	
	void Update() {
		if(Input.GetMouseButton(0)){
			float xAngle = Mathf.SmoothDampAngle(transform.eulerAngles.x, target.eulerAngles.x, ref xVelocity, smooth);
			float yAngle = Mathf.SmoothDampAngle(transform.eulerAngles.y, target.eulerAngles.y, ref yVelocity, smooth);
			Vector3 position = target.position;
			position += Quaternion.Euler(xAngle, yAngle, 0) * new Vector3(0, 0, -distance);
			transform.position = position;
			transform.LookAt(target);
		} else {
			xVelocity = 0.0F;
			yVelocity = 0.0F;
		}
	}
}

This by its self does not move the camera, but more centers it behind an object. This peice of code I did for someone on the forum here and it will rotate and object.

var timeScale=1;

function Update () {
	LookAround();
	
	timeScale=1;
	if(Input.GetKey("space")) timeScale=0.2;
	Time.timeScale=Mathf.Lerp(Time.timeScale, timeScale, 4 * Time.deltaTime);
}


function LookAround() {
	var x = Input.GetAxis ("Mouse X");
	var y = -Input.GetAxis ("Mouse Y");
	
	transform.Rotate (y * 4, x * 4, 0);
	//x=transform.localEulerAngles.y;
	//if(x>180) x-=360;
	//x=Mathf.Clamp(x, -80, 80);
	//transform.localEulerAngles.y=x;
	transform.localEulerAngles.z=0;
}

Now, having a look at the concept from a MouseOrbit standpoint… I believe changes to it will help alot more than working on it in this state:

var target : Transform;
var distance = 10.0;

var useMouse=true;
var damping=4.0;

var xSpeed = 250.0;
var ySpeed = 120.0;

var yMinLimit = -20;
var yMaxLimit = 80;

private var x = 0.0;
private var y = 0.0;
private var targetPosition : Vector3=Vector3.zero;

function Start () {
    var angles = transform.eulerAngles;
    x = angles.y;
    y = angles.x;

	// Make the rigid body not change rotation
   	if (rigidbody)
		rigidbody.freezeRotation = true;
	if(!target)targetPosition=transform.position;
}

function LateUpdate () {
	var targ=targetPosition;
    if (target) targ=target.position;
    
    if(Input.GetMouseButton(0) || !useMouse){
		x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
		y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;
	 	
	 	y = ClampAngle(y, yMinLimit, yMaxLimit);
 	}
 	
	var rotation = Quaternion.Euler(y, x, 0);
	rotation=Quaternion.Slerp(transform.rotation, rotation, damping * Time.deltaTime);
	var position = rotation * Vector3(0.0, 0.0, -distance) + targ;
	
	transform.rotation = rotation;
	transform.position = position;
}

static function ClampAngle (angle : float, min : float, max : float) {
	if (angle < -360)
		angle += 360;
	if (angle > 360)
		angle -= 360;
	return Mathf.Clamp (angle, min, max);
}

Hope it helps

Many thanks BigMisterB for the comprehensive and detailed reply, really is very kind of you to take the time to put such a full answer together. Your solution looks pretty much like what I have for the rest of my orbit code and it all works very nicely, but I am just at a loss still as to why SmoothDamp behaves oddly. Just wondering if a bug report is needed but it seems that it cant be a bug as surely someone else would have spotted the behavior in the years gone by, so I am left wondering if SmoothDamp just isnt designed o work with negative velocities.

Thanks again for your time :slight_smile:
Chris

I would not believe that it is a bug though. There are a couple of things that you are not doing in your script. Like clearing vx and vy. When you stop, you need to release all of the momentum.

I also do not believe it is a bug since I actually got the SmoothDampAngle to work properly, I was very unhappy with the results though.

Chris-

I’m seeing a problem with SmoothDamp too. I’ve recently upgraded to 3.4, but I’m not sure if this is the problem yet. I use SmoothDamp to control the camera rotation in a racing game I’m working on for iOS. If I shake the device from side to side (causing my inputX variable to go from positive to negative quickly), SmoothDamp returns NaN the camera dies. I didn’t notice this problem with Unity 3.3.

Here’s the code I use to set the camera’s rotation.

//this is JavaScript code
//cameraRotation is the Z rotation of the camera
//inputX is the raw accelerometer input value
//cameraBankScale  inputMaxX are scale factors for the inputX variable
//cameraRotationDamping is the time delay for SmoothDamp
	cameraRotation = Mathf.SmoothDamp(cameraRotation, inputX * cameraBankScale/inputMaxX, inputRotationVelocity, cameraRotationDamping);
	Camera.main.transform.localEulerAngles.z = cameraRotation;

I found my NaN problem and it wasn’t SmoothDamp. My inputX variable above wasn’t actually the raw accelerometer input. Instead I was doing a Mathf.Asin on it to return a rotation angle of the phone. I didn’t bound the inputs of the Asin input to +/-1. When the accelerometer input was greater to 1 or less than -1, Asin returns a NaN (since arcsin of anything out of those bounds isn’t a real number).

I didn’t run into this problem in Unity3.3 and it happens all the time now, so it may be possible that the new UnityScript compiler doesn’t check for an invalid input range on Asin. However, it’s more likely that I just wasn’t looking for it. Either way, lesson learned → don’t feed a function an input value that doesn’t make sense!

Have fun,

Sorry for digging up an old thread, but everywhere I’ve searched and found this issue it has been left unresolved.

I’m having the same problem as SpookyCat where a SmoothDamp or SmoothDampAngle in a negative direction does not result in a smooth stop with mouse axis/raw inputs.

I ran into this bug in version 2020.1, but it seems that, despite this 10-year-old thread, nobody ever actually submitted a bug report on it.

So here we go: Unity Issue Tracker - Vector3.SmoothDamp behaves differently between positive and negative velocity

1 Like

SmoothDampe Angle worked fine in my old project last year, when I implemented the same code again, recently the negative angle are not damping at all

1 Like

I wound up rolling my own copy of the SmoothDamp/SmoothDampAngle code to disable the “feature” that causes the problem:

    #region smoothDamp
    //Copied directly from Unity source, but removes their "Prevent Overshooting" code,
    //which only works on negative velocities (snaps them to an instant halt, which I don't want)
    public static float SmoothDampAngle(float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed)
    {
        float deltaTime = Time.deltaTime;
        return SmoothDampAngle(current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime);
    }

    // Gradually changes an angle given in degrees towards a desired goal angle over time.
    public static float SmoothDampAngle(float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed, float deltaTime)
    {
        target = current + Mathf.DeltaAngle(current, target);
        return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime);
    }

    // Gradually changes a value towards a desired goal over time.
    public static float SmoothDamp(float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed, float deltaTime)
    {
        // Based on Game Programming Gems 4 Chapter 1.10
        smoothTime = Mathf.Max(0.0001F, smoothTime);
        float omega = 2F / smoothTime;

        float x = omega * deltaTime;
        float exp = 1F / (1F + x + 0.48F * x * x + 0.235F * x * x * x);
        float change = current - target;
        //float originalTo = target;

        // Clamp maximum speed
        float maxChange = maxSpeed * smoothTime;
        change = Mathf.Clamp(change, -maxChange, maxChange);
        target = current - change;

        float temp = (currentVelocity + omega * change) * deltaTime;
        currentVelocity = (currentVelocity - omega * temp) * exp;

        float output = target + (change + temp) * exp;

        //// Prevent overshooting
        //if (originalTo - current > 0.0F == output > originalTo)
        //{
        //    output = originalTo;
        //    currentVelocity = (output - originalTo) / deltaTime;
        //}

        return output;
    }
    #endregion

Instead of removing the overshoot protection, I modified it. For me, this code works fine with negative velocity:

public static float SmoothDamp(float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed, float deltaTime)
{
   // Based on Game Programming Gems 4 Chapter 1.10
   smoothTime = Mathf.Max(0.0001F, smoothTime);
   float omega = 2f / smoothTime;
   float x = omega * deltaTime;
   float exp = 1F / (1F + x + 0.48F * x * x + 0.235F * x * x * x);
   float change = current - target;
   // Clamp maximum speed
   float maxChange = maxSpeed * smoothTime;
   change = Mathf.Clamp(change, -maxChange, maxChange);
   float temp = (currentVelocity + omega * change) * deltaTime;
   currentVelocity = (currentVelocity - omega * temp) * exp;
   float output = current - change + (change + temp) * exp;
  
   // Prevent overshooting
   if ((target - current) * (output - target) > 0f)
   //if (target - current > 0.0F == output > target) <- old
   {
      output = target;
      currentVelocity = (output - target) / deltaTime;
   }
   return output;
}
4 Likes

I’ve spent some time looking into this issue. It seems that the intended behaviour was to bring the motion to a halt if it overshot the target. But it mostly misses detecting that situation. The more common case (which it always misses) is when a moving target sweeps past the current position. I’ve published a write up with a sample Unity project for playing around with the behavour. The most comprehensive fix is to add the previous target position as an input to the function. This allows it to properly detect the moving target case.

https://github.com/LSBUGPG/SmoothDamp

    public static float SmoothDampMovingTarget(float current, float target, ref float currentVelocity, float previousTarget, float smoothTime, float maxSpeed, float deltaTime)
    {
        float output;
        if (target == current || (previousTarget < current && current < target) || (previousTarget > current && current > target))
        {
            // currently on target or target is passing through
            output = current;
            currentVelocity = 0f;
        }
        else
        {
          
            // apply original smoothing
            output = Mathf.SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime);
            if ((target > current && output > target) || (target < current && output < target))
            {
                // we have overshot the target
                output = target;
                currentVelocity = 0f;
            }
        }
        return output;
    }