001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.math.geometry;
006
007import com.fasterxml.jackson.annotation.JsonAutoDetect;
008import com.fasterxml.jackson.annotation.JsonCreator;
009import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
010import com.fasterxml.jackson.annotation.JsonProperty;
011import edu.wpi.first.math.VecBuilder;
012import edu.wpi.first.math.Vector;
013import edu.wpi.first.math.geometry.proto.QuaternionProto;
014import edu.wpi.first.math.geometry.struct.QuaternionStruct;
015import edu.wpi.first.math.numbers.N3;
016import edu.wpi.first.util.protobuf.ProtobufSerializable;
017import edu.wpi.first.util.struct.StructSerializable;
018import java.util.Objects;
019
020/** Represents a quaternion. */
021@JsonIgnoreProperties(ignoreUnknown = true)
022@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
023public class Quaternion implements ProtobufSerializable, StructSerializable {
024  // Scalar r in versor form
025  private final double m_w;
026
027  // Vector v in versor form
028  private final double m_x;
029  private final double m_y;
030  private final double m_z;
031
032  /** Constructs a quaternion with a default angle of 0 degrees. */
033  public Quaternion() {
034    m_w = 1.0;
035    m_x = 0.0;
036    m_y = 0.0;
037    m_z = 0.0;
038  }
039
040  /**
041   * Constructs a quaternion with the given components.
042   *
043   * @param w W component of the quaternion.
044   * @param x X component of the quaternion.
045   * @param y Y component of the quaternion.
046   * @param z Z component of the quaternion.
047   */
048  @JsonCreator
049  public Quaternion(
050      @JsonProperty(required = true, value = "W") double w,
051      @JsonProperty(required = true, value = "X") double x,
052      @JsonProperty(required = true, value = "Y") double y,
053      @JsonProperty(required = true, value = "Z") double z) {
054    m_w = w;
055    m_x = x;
056    m_y = y;
057    m_z = z;
058  }
059
060  /**
061   * Adds another quaternion to this quaternion entrywise.
062   *
063   * @param other The other quaternion.
064   * @return The quaternion sum.
065   */
066  public Quaternion plus(Quaternion other) {
067    return new Quaternion(
068        getW() + other.getW(), getX() + other.getX(), getY() + other.getY(), getZ() + other.getZ());
069  }
070
071  /**
072   * Subtracts another quaternion from this quaternion entrywise.
073   *
074   * @param other The other quaternion.
075   * @return The quaternion difference.
076   */
077  public Quaternion minus(Quaternion other) {
078    return new Quaternion(
079        getW() - other.getW(), getX() - other.getX(), getY() - other.getY(), getZ() - other.getZ());
080  }
081
082  /**
083   * Divides by a scalar.
084   *
085   * @param scalar The value to scale each component by.
086   * @return The scaled quaternion.
087   */
088  public Quaternion divide(double scalar) {
089    return new Quaternion(getW() / scalar, getX() / scalar, getY() / scalar, getZ() / scalar);
090  }
091
092  /**
093   * Multiplies with a scalar.
094   *
095   * @param scalar The value to scale each component by.
096   * @return The scaled quaternion.
097   */
098  public Quaternion times(double scalar) {
099    return new Quaternion(getW() * scalar, getX() * scalar, getY() * scalar, getZ() * scalar);
100  }
101
102  /**
103   * Multiply with another quaternion.
104   *
105   * @param other The other quaternion.
106   * @return The quaternion product.
107   */
108  public Quaternion times(Quaternion other) {
109    // https://en.wikipedia.org/wiki/Quaternion#Scalar_and_vector_parts
110    final var r1 = m_w;
111    final var r2 = other.m_w;
112
113    // v₁ ⋅ v₂
114    double dot = m_x * other.m_x + m_y * other.m_y + m_z * other.m_z;
115
116    // v₁ x v₂
117    double cross_x = m_y * other.m_z - other.m_y * m_z;
118    double cross_y = other.m_x * m_z - m_x * other.m_z;
119    double cross_z = m_x * other.m_y - other.m_x * m_y;
120
121    return new Quaternion(
122        // r = r₁r₂ − v₁ ⋅ v₂
123        r1 * r2 - dot,
124        // v = r₁v₂ + r₂v₁ + v₁ x v₂
125        r1 * other.m_x + r2 * m_x + cross_x,
126        r1 * other.m_y + r2 * m_y + cross_y,
127        r1 * other.m_z + r2 * m_z + cross_z);
128  }
129
130  @Override
131  public String toString() {
132    return String.format("Quaternion(%s, %s, %s, %s)", getW(), getX(), getY(), getZ());
133  }
134
135  /**
136   * Checks equality between this Quaternion and another object.
137   *
138   * @param obj The other object.
139   * @return Whether the two objects are equal or not.
140   */
141  @Override
142  public boolean equals(Object obj) {
143    if (obj instanceof Quaternion) {
144      var other = (Quaternion) obj;
145
146      return Math.abs(dot(other) - norm() * other.norm()) < 1e-9
147          && Math.abs(norm() - other.norm()) < 1e-9;
148    }
149    return false;
150  }
151
152  @Override
153  public int hashCode() {
154    return Objects.hash(m_w, m_x, m_y, m_z);
155  }
156
157  /**
158   * Returns the conjugate of the quaternion.
159   *
160   * @return The conjugate quaternion.
161   */
162  public Quaternion conjugate() {
163    return new Quaternion(getW(), -getX(), -getY(), -getZ());
164  }
165
166  /**
167   * Returns the elementwise product of two quaternions.
168   *
169   * @param other The other quaternion.
170   * @return The dot product of two quaternions.
171   */
172  public double dot(final Quaternion other) {
173    return getW() * other.getW()
174        + getX() * other.getX()
175        + getY() * other.getY()
176        + getZ() * other.getZ();
177  }
178
179  /**
180   * Returns the inverse of the quaternion.
181   *
182   * @return The inverse quaternion.
183   */
184  public Quaternion inverse() {
185    var norm = norm();
186    return conjugate().divide(norm * norm);
187  }
188
189  /**
190   * Calculates the L2 norm of the quaternion.
191   *
192   * @return The L2 norm.
193   */
194  public double norm() {
195    return Math.sqrt(dot(this));
196  }
197
198  /**
199   * Normalizes the quaternion.
200   *
201   * @return The normalized quaternion.
202   */
203  public Quaternion normalize() {
204    double norm = norm();
205    if (norm == 0.0) {
206      return new Quaternion();
207    } else {
208      return new Quaternion(getW() / norm, getX() / norm, getY() / norm, getZ() / norm);
209    }
210  }
211
212  /**
213   * Rational power of a quaternion.
214   *
215   * @param t the power to raise this quaternion to.
216   * @return The quaternion power
217   */
218  public Quaternion pow(double t) {
219    // q^t = e^(ln(q^t)) = e^(t * ln(q))
220    return this.log().times(t).exp();
221  }
222
223  /**
224   * Matrix exponential of a quaternion.
225   *
226   * @param adjustment the "Twist" that will be applied to this quaternion.
227   * @return The quaternion product of exp(adjustment) * this
228   */
229  public Quaternion exp(Quaternion adjustment) {
230    return adjustment.exp().times(this);
231  }
232
233  /**
234   * Matrix exponential of a quaternion.
235   *
236   * <p>source: wpimath/algorithms.md
237   *
238   * <p>If this quaternion is in 𝖘𝖔(3) and you are looking for an element of SO(3), use {@link
239   * fromRotationVector}
240   *
241   * @return The Matrix exponential of this quaternion.
242   */
243  public Quaternion exp() {
244    var scalar = Math.exp(getW());
245
246    var axial_magnitude = Math.sqrt(getX() * getX() + getY() * getY() + getZ() * getZ());
247    var cosine = Math.cos(axial_magnitude);
248
249    double axial_scalar;
250
251    if (axial_magnitude < 1e-9) {
252      // Taylor series of sin(θ) / θ near θ = 0: 1 − θ²/6 + θ⁴/120 + O(n⁶)
253      var axial_magnitude_sq = axial_magnitude * axial_magnitude;
254      var axial_magnitude_sq_sq = axial_magnitude_sq * axial_magnitude_sq;
255      axial_scalar = 1.0 - axial_magnitude_sq / 6.0 + axial_magnitude_sq_sq / 120.0;
256    } else {
257      axial_scalar = Math.sin(axial_magnitude) / axial_magnitude;
258    }
259
260    return new Quaternion(
261        cosine * scalar,
262        getX() * axial_scalar * scalar,
263        getY() * axial_scalar * scalar,
264        getZ() * axial_scalar * scalar);
265  }
266
267  /**
268   * Log operator of a quaternion.
269   *
270   * @param end The quaternion to map this quaternion onto.
271   * @return The "Twist" that maps this quaternion to the argument.
272   */
273  public Quaternion log(Quaternion end) {
274    return end.times(this.inverse()).log();
275  }
276
277  /**
278   * The Log operator of a general quaternion.
279   *
280   * <p>source: wpimath/algorithms.md
281   *
282   * <p>If this quaternion is in SO(3) and you are looking for an element of 𝖘𝖔(3), use {@link
283   * toRotationVector}
284   *
285   * @return The logarithm of this quaternion.
286   */
287  public Quaternion log() {
288    var norm = norm();
289    var scalar = Math.log(norm);
290
291    var v_norm = Math.sqrt(getX() * getX() + getY() * getY() + getZ() * getZ());
292
293    var s_norm = getW() / norm;
294
295    if (Math.abs(s_norm + 1) < 1e-9) {
296      return new Quaternion(scalar, -Math.PI, 0, 0);
297    }
298
299    double v_scalar;
300
301    if (v_norm < 1e-9) {
302      // Taylor series expansion of atan2(y / x) / y around y = 0 => 1/x - y²/3*x³ + O(y⁴)
303      v_scalar = 1.0 / getW() - 1.0 / 3.0 * v_norm * v_norm / (getW() * getW() * getW());
304    } else {
305      v_scalar = Math.atan2(v_norm, getW()) / v_norm;
306    }
307
308    return new Quaternion(scalar, v_scalar * getX(), v_scalar * getY(), v_scalar * getZ());
309  }
310
311  /**
312   * Returns W component of the quaternion.
313   *
314   * @return W component of the quaternion.
315   */
316  @JsonProperty(value = "W")
317  public double getW() {
318    return m_w;
319  }
320
321  /**
322   * Returns X component of the quaternion.
323   *
324   * @return X component of the quaternion.
325   */
326  @JsonProperty(value = "X")
327  public double getX() {
328    return m_x;
329  }
330
331  /**
332   * Returns Y component of the quaternion.
333   *
334   * @return Y component of the quaternion.
335   */
336  @JsonProperty(value = "Y")
337  public double getY() {
338    return m_y;
339  }
340
341  /**
342   * Returns Z component of the quaternion.
343   *
344   * @return Z component of the quaternion.
345   */
346  @JsonProperty(value = "Z")
347  public double getZ() {
348    return m_z;
349  }
350
351  /**
352   * Returns the quaternion representation of this rotation vector.
353   *
354   * <p>This is also the exp operator of 𝖘𝖔(3).
355   *
356   * <p>source: wpimath/algorithms.md
357   *
358   * @param rvec The rotation vector.
359   * @return The quaternion representation of this rotation vector.
360   */
361  public static Quaternion fromRotationVector(Vector<N3> rvec) {
362    double theta = rvec.norm();
363
364    double cos = Math.cos(theta / 2);
365
366    double axial_scalar;
367
368    if (theta < 1e-9) {
369      // taylor series expansion of sin(θ/2) / θ = 1/2 - θ²/48 + O(θ⁴)
370      axial_scalar = 1.0 / 2.0 - theta * theta / 48.0;
371    } else {
372      axial_scalar = Math.sin(theta / 2) / theta;
373    }
374
375    return new Quaternion(
376        cos,
377        axial_scalar * rvec.get(0, 0),
378        axial_scalar * rvec.get(1, 0),
379        axial_scalar * rvec.get(2, 0));
380  }
381
382  /**
383   * Returns the rotation vector representation of this quaternion.
384   *
385   * <p>This is also the log operator of SO(3).
386   *
387   * @return The rotation vector representation of this quaternion.
388   */
389  public Vector<N3> toRotationVector() {
390    // See equation (31) in "Integrating Generic Sensor Fusion Algorithms with
391    // Sound State Representation through Encapsulation of Manifolds"
392    //
393    // https://arxiv.org/pdf/1107.1119.pdf
394    double norm = Math.sqrt(getX() * getX() + getY() * getY() + getZ() * getZ());
395
396    double coeff;
397    if (norm < 1e-9) {
398      coeff = 2.0 / getW() - 2.0 / 3.0 * norm * norm / (getW() * getW() * getW());
399    } else {
400      if (getW() < 0.0) {
401        coeff = 2.0 * Math.atan2(-norm, -getW()) / norm;
402      } else {
403        coeff = 2.0 * Math.atan2(norm, getW()) / norm;
404      }
405    }
406
407    return VecBuilder.fill(coeff * getX(), coeff * getY(), coeff * getZ());
408  }
409
410  /** Quaternion protobuf for serialization. */
411  public static final QuaternionProto proto = new QuaternionProto();
412
413  /** Quaternion struct for serialization. */
414  public static final QuaternionStruct struct = new QuaternionStruct();
415}