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