Slerp.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.numbers.quaternion;

  18. import java.util.function.DoubleFunction;

  19. /**
  20.  * Perform spherical linear interpolation (<a href="https://en.wikipedia.org/wiki/Slerp">Slerp</a>).
  21.  *
  22.  * The <em>Slerp</em> algorithm is designed to interpolate smoothly between
  23.  * two rotations/orientations, producing a constant-speed motion along an arc.
  24.  * The original purpose of this algorithm was to animate 3D rotations. All output
  25.  * quaternions are in positive polar form, meaning a unit quaternion with a positive
  26.  * scalar component.
  27.  */
  28. public class Slerp implements DoubleFunction<Quaternion> {
  29.     /**
  30.      * Threshold max value for the dot product.
  31.      * If the quaternion dot product is greater than this value (i.e. the
  32.      * quaternions are very close to each other), then the quaternions are
  33.      * linearly interpolated instead of spherically interpolated.
  34.      */
  35.     private static final double MAX_DOT_THRESHOLD = 0.9995;
  36.     /** Start of the interpolation. */
  37.     private final Quaternion start;
  38.     /** End of the interpolation. */
  39.     private final Quaternion end;
  40.     /** Linear or spherical interpolation algorithm. */
  41.     private final DoubleFunction<Quaternion> algo;

  42.     /**
  43.      * Create an instance.
  44.      *
  45.      * @param start Start of the interpolation.
  46.      * @param end End of the interpolation.
  47.      */
  48.     public Slerp(Quaternion start,
  49.                  Quaternion end) {
  50.         this.start = start.positivePolarForm();

  51.         final Quaternion e = end.positivePolarForm();
  52.         double dot = this.start.dot(e);

  53.         // If the dot product is negative, then the interpolation won't follow the shortest
  54.         // angular path between the two quaterions. In this case, invert the end quaternion
  55.         // to produce an equivalent rotation that will give us the path we want.
  56.         if (dot < 0) {
  57.             dot = -dot;
  58.             this.end = e.negate();
  59.         } else {
  60.             this.end = e;
  61.         }

  62.         algo = dot > MAX_DOT_THRESHOLD ?
  63.             new Linear() :
  64.             new Spherical(dot);
  65.     }

  66.     /**
  67.      * Performs the interpolation.
  68.      * The rotation returned by this method is controlled by the interpolation parameter, {@code t}.
  69.      * All other values are interpolated (or extrapolated if {@code t} is outside of the
  70.      * {@code [0, 1]} range). The returned quaternion is in positive polar form, meaning that it
  71.      * is a unit quaternion with a positive scalar component.
  72.      *
  73.      * @param t Interpolation control parameter.
  74.      * When {@code t = 0}, a rotation equal to the start instance is returned.
  75.      * When {@code t = 1}, a rotation equal to the end instance is returned.
  76.      * @return an interpolated quaternion in positive polar form.
  77.      */
  78.     @Override
  79.     public Quaternion apply(double t) {
  80.         // Handle no-op cases.
  81.         if (t == 0) {
  82.             return start;
  83.         } else if (t == 1) {
  84.             // Call to "positivePolarForm()" is required because "end" might
  85.             // not be in positive polar form.
  86.             return end.positivePolarForm();
  87.         }

  88.         return algo.apply(t);
  89.     }

  90.     /**
  91.      * Linear interpolation, used when the quaternions are too closely aligned.
  92.      */
  93.     private final class Linear implements DoubleFunction<Quaternion> {
  94.         /** Package-private constructor. */
  95.         Linear() {}

  96.         /** {@inheritDoc} */
  97.         @Override
  98.         public Quaternion apply(double t) {
  99.             final double f = 1 - t;
  100.             return Quaternion.of(f * start.getW() + t * end.getW(),
  101.                                  f * start.getX() + t * end.getX(),
  102.                                  f * start.getY() + t * end.getY(),
  103.                                  f * start.getZ() + t * end.getZ()).positivePolarForm();
  104.         }
  105.     }

  106.     /**
  107.      * Spherical interpolation, used when the quaternions are too closely aligned.
  108.      * When we may end up dividing by zero (cf. 1/sin(theta) term below).
  109.      * {@link Linear} interpolation must be used.
  110.      */
  111.     private final class Spherical implements DoubleFunction<Quaternion> {
  112.         /** Angle of rotation. */
  113.         private final double theta;
  114.         /** Sine of {@link #theta}. */
  115.         private final double sinTheta;

  116.         /**
  117.          * @param dot Dot product of the start and end quaternions.
  118.          */
  119.         Spherical(double dot) {
  120.             theta = Math.acos(dot);
  121.             sinTheta = Math.sin(theta);
  122.         }

  123.         /** {@inheritDoc} */
  124.         @Override
  125.         public Quaternion apply(double t) {
  126.             final double f1 = Math.sin((1 - t) * theta) / sinTheta;
  127.             final double f2 = Math.sin(t * theta) / sinTheta;

  128.             return Quaternion.of(f1 * start.getW() + f2 * end.getW(),
  129.                                  f1 * start.getX() + f2 * end.getX(),
  130.                                  f1 * start.getY() + f2 * end.getY(),
  131.                                  f1 * start.getZ() + f2 * end.getZ()).positivePolarForm();
  132.         }
  133.     }
  134. }