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 19 import java.util.function.DoubleFunction; 20 21 /** 22 * Perform spherical linear interpolation (<a href="https://en.wikipedia.org/wiki/Slerp">Slerp</a>). 23 * 24 * The <em>Slerp</em> algorithm is designed to interpolate smoothly between 25 * two rotations/orientations, producing a constant-speed motion along an arc. 26 * The original purpose of this algorithm was to animate 3D rotations. All output 27 * quaternions are in positive polar form, meaning a unit quaternion with a positive 28 * scalar component. 29 */ 30 public class Slerp implements DoubleFunction<Quaternion> { 31 /** 32 * Threshold max value for the dot product. 33 * If the quaternion dot product is greater than this value (i.e. the 34 * quaternions are very close to each other), then the quaternions are 35 * linearly interpolated instead of spherically interpolated. 36 */ 37 private static final double MAX_DOT_THRESHOLD = 0.9995; 38 /** Start of the interpolation. */ 39 private final Quaternion start; 40 /** End of the interpolation. */ 41 private final Quaternion end; 42 /** Linear or spherical interpolation algorithm. */ 43 private final DoubleFunction<Quaternion> algo; 44 45 /** 46 * Create an instance. 47 * 48 * @param start Start of the interpolation. 49 * @param end End of the interpolation. 50 */ 51 public Slerp(Quaternion start, 52 Quaternion end) { 53 this.start = start.positivePolarForm(); 54 55 final Quaternion e = end.positivePolarForm(); 56 double dot = this.start.dot(e); 57 58 // If the dot product is negative, then the interpolation won't follow the shortest 59 // angular path between the two quaterions. In this case, invert the end quaternion 60 // to produce an equivalent rotation that will give us the path we want. 61 if (dot < 0) { 62 dot = -dot; 63 this.end = e.negate(); 64 } else { 65 this.end = e; 66 } 67 68 algo = dot > MAX_DOT_THRESHOLD ? 69 new Linear() : 70 new Spherical(dot); 71 } 72 73 /** 74 * Performs the interpolation. 75 * The rotation returned by this method is controlled by the interpolation parameter, {@code t}. 76 * All other values are interpolated (or extrapolated if {@code t} is outside of the 77 * {@code [0, 1]} range). The returned quaternion is in positive polar form, meaning that it 78 * is a unit quaternion with a positive scalar component. 79 * 80 * @param t Interpolation control parameter. 81 * When {@code t = 0}, a rotation equal to the start instance is returned. 82 * When {@code t = 1}, a rotation equal to the end instance is returned. 83 * @return an interpolated quaternion in positive polar form. 84 */ 85 @Override 86 public Quaternion apply(double t) { 87 // Handle no-op cases. 88 if (t == 0) { 89 return start; 90 } else if (t == 1) { 91 // Call to "positivePolarForm()" is required because "end" might 92 // not be in positive polar form. 93 return end.positivePolarForm(); 94 } 95 96 return algo.apply(t); 97 } 98 99 /** 100 * Linear interpolation, used when the quaternions are too closely aligned. 101 */ 102 private final class Linear implements DoubleFunction<Quaternion> { 103 /** Package-private constructor. */ 104 Linear() {} 105 106 /** {@inheritDoc} */ 107 @Override 108 public Quaternion apply(double t) { 109 final double f = 1 - t; 110 return Quaternion.of(f * start.getW() + t * end.getW(), 111 f * start.getX() + t * end.getX(), 112 f * start.getY() + t * end.getY(), 113 f * start.getZ() + t * end.getZ()).positivePolarForm(); 114 } 115 } 116 117 /** 118 * Spherical interpolation, used when the quaternions are too closely aligned. 119 * When we may end up dividing by zero (cf. 1/sin(theta) term below). 120 * {@link Linear} interpolation must be used. 121 */ 122 private final class Spherical implements DoubleFunction<Quaternion> { 123 /** Angle of rotation. */ 124 private final double theta; 125 /** Sine of {@link #theta}. */ 126 private final double sinTheta; 127 128 /** 129 * @param dot Dot product of the start and end quaternions. 130 */ 131 Spherical(double dot) { 132 theta = Math.acos(dot); 133 sinTheta = Math.sin(theta); 134 } 135 136 /** {@inheritDoc} */ 137 @Override 138 public Quaternion apply(double t) { 139 final double f1 = Math.sin((1 - t) * theta) / sinTheta; 140 final double f2 = Math.sin(t * theta) / sinTheta; 141 142 return Quaternion.of(f1 * start.getW() + f2 * end.getW(), 143 f1 * start.getX() + f2 * end.getX(), 144 f1 * start.getY() + f2 * end.getY(), 145 f1 * start.getZ() + f2 * end.getZ()).positivePolarForm(); 146 } 147 } 148 }