Search code examples
javaandroidvectorlibgdxeasing

Easing a Vector3 to a new position


Not sure if libgdx offers this functionality, so it might be a maths question. I have various 3D camera coordinates stored as a Vector3, and I want to move the camera from one to the other.

Vector3 CAM_POSITION_DEFAULT = new Vector3(0f, 1f, -5f);
Vector3 CAM_POSITION_TOP = new Vector3(0f, 10f, -5f);

I could just set the camera position to the new one, but this is a but too sharp. A nice smooth transition, with some easing (slow start, fast mid, slow end) would be ideal.

Is this just a simple matrix manipulation, or is there a tidy way to get this functionality with easing? Secondly, if easing is available, is it possible to use different forms of easing (like in javascript's jQuery library)?

Many thanks.


Solution

  • What you are looking for is a simple linear interpolation between the two positions.

    To achieve this, you can use libgdx's provided lerp(...) method.

    If you want to calculate the positions inbetween CAM_POSITION_DEFAULT and CAM_POSITION_TOP, you just need to interpolate between the two vectors:

    Vector3 inbetween = CAM_POSITION_DEFAULT.lerp(CAM_POSITION_TOP, alpha);
    

    If you provide a value of alpha=0, you will get CAM_POSITION_DEFAULT returned, if you provide alpha=1, you will get CAM_POSITION_TOP... alpha=0.5f will therefore be the point directly in the middle of the two positions...

    The trick now is how to choose the alpha value for a smooth transition that takes a defined amount of seconds:

    class CameraTransition {
    
        private final Vector3 startPos;
        private final Vector3 goalPos;
        private final float duration;
    
        private float stateTime = 0.0f;
    
        public CameraTransition(final Vector3 startPos, final Vector3 goalPos, float duration) {
            this.startPos = startPos;
            this.goalPos = goalPos;
            this.duration = duration;
        }
    
        public Vector3 act(float delta) {
            stateTime += delta;
    
            return startPos.lerp(goalPos, MathUtils.clamp(stateTime/duration, 0.0f,1.0f));
        }
    
    }
    

    So you could use this code (not tested, sorry)... Create a new CameraTransition and set the duration.

    Then within your render(float delta) you just have to call the act() method and pass the delta time. Of course you should only call the act() method when you want the transition to run.

    Hope this helps... :)

    UPDATE:

    After seeing that you are looking for a smooth transition which starts slowly and ends slowly, I have changed the interpolation to something that uses a smoother curve instead of linear interpolation:

    class CameraTransition {
    
            private final Vector3 startPos;
            private final Vector3 goalPos;
            private final float duration;
    
            private float stateTime = 0.0f;
            private float alpha;
    
            public CameraTransition(final Vector3 startPos, final Vector3 goalPos, float duration) {
                this.startPos = startPos;
                this.goalPos = goalPos;
                this.duration = duration;
            }
    
            public Vector3 act(float delta) {
                stateTime += delta;
    
                alpha = MathUtils.clamp(stateTime/duration, 0.0f,1.0f);
    
    
                return startPos.lerp(goalPos, -2 * alpha*alpha*alpha + 3 * alpha*alpha);
            }
    
        }
    

    In this version, I am chaning the linear interpolation to use this curve:

    f(x) = -2*alpha^3 + 3*alpha^2
    

    http://www.wolframalpha.com/input/?i=plot+f%28x%29+%3D+-2x%5E3+%2B3x%5E2+from+0+to+1