File indexing completed on 2024-05-19 04:45:53

0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT
0003 
0004 // Own header
0005 #include "polarpointf.h"
0006 
0007 // Other includes
0008 #include "helpermath.h"
0009 #include "helperposixmath.h"
0010 #include <cmath>
0011 #include <qdebug.h>
0012 #include <qmath.h>
0013 #include <type_traits>
0014 
0015 namespace PerceptualColor
0016 {
0017 /** @brief Constructor
0018  *
0019  * Normalizes the given polar coordinates and constructs an object with
0020  * the <em>normalized</em> polar coordinates. See the general class
0021  * description for details about the normalization.
0022  *
0023  * @param newRadius the @ref radius() value
0024  * @param newAngleDegree the @ref angleDegree() value */
0025 PolarPointF::PolarPointF(const double newRadius, const double newAngleDegree)
0026     : m_angleDegree(newAngleDegree)
0027     , m_radius(newRadius)
0028 {
0029     normalizePolar360(m_radius, m_angleDegree);
0030 }
0031 
0032 /** @brief Constructor
0033  *
0034  * Constructs an object converting from the given Cartesian coordinates.
0035  *
0036  * If the Cartesian coordinates are (0, 0) than the @ref angleDegree (which is
0037  * meaningless for a @ref radius of 0) is set to 0°.
0038  *
0039  * @param cartesianCoordiantes the Cartesian coordinates */
0040 PolarPointF::PolarPointF(const QPointF cartesianCoordiantes)
0041 {
0042     m_radius = sqrt( //
0043         pow(cartesianCoordiantes.x(), 2) + pow(cartesianCoordiantes.y(), 2));
0044     if (m_radius == 0) {
0045         m_angleDegree = 0;
0046         return;
0047     }
0048     // NOTE As explained in https://en.wikipedia.org/wiki/Atan2
0049     // the function std::atan2() can be used to calculate the angle without
0050     // an “if” statement. However, it returns angles in the interval [-π, +π]
0051     // while we need [0°, 360°], so finally we could avoind an “if” statement
0052     // but need to add code for the range correction. Therefore, the current
0053     // approach seems fine.
0054     if (cartesianCoordiantes.y() >= 0) {
0055         m_angleDegree = qRadiansToDegrees( //
0056             acos(cartesianCoordiantes.x() / m_radius));
0057     } else {
0058         m_angleDegree = qRadiansToDegrees( //
0059             2 * pi - acos(cartesianCoordiantes.x() / m_radius));
0060     }
0061 }
0062 
0063 /** @brief Compares with another @ref PolarPointF
0064  *
0065  * @param other the polar coordinates to compare with
0066  *
0067  * @returns <tt>true</tt> if both, <tt>this</tt> and <tt>other</tt>,
0068  * are the same point in the coordinate space. <tt>false</tt> otherwise.
0069  * Therefore <tt>[@ref radius() 0, @ref angleDegree() 50]</tt> is considered
0070  * to be the same point as <tt>[@ref radius() 0, @ref angleDegree() 80]</tt>
0071    because the @ref angleDegree() is meaningless if the @ref radius() is 0.*/
0072 bool PolarPointF::isSamePoint(const PolarPointF other) const
0073 {
0074     return (
0075         // radius has to be identical
0076         (m_radius == other.m_radius) &&
0077         // angle has to be identical (except when radius is 0, because
0078         // then angle is meaningless)
0079         ((m_angleDegree == other.m_angleDegree) || (m_radius == 0)));
0080 }
0081 
0082 /** @brief Normalized radius
0083  *
0084  * @returns the normalized radius value, guaranteed to be ≥ 0. */
0085 double PolarPointF::radius() const
0086 {
0087     return m_radius;
0088 }
0089 
0090 /** @brief Normalized angle
0091  *
0092  * @returns the normalized angle value (coordinates in degree), guaranteed to
0093  * be 0° ≤ value < 360° */
0094 double PolarPointF::angleDegree() const
0095 {
0096     return m_angleDegree;
0097 }
0098 
0099 /** @brief Convert to Cartesian coordinates
0100  *
0101  * @returns the corresponding Cartesian coordinates */
0102 QPointF PolarPointF::toCartesian() const
0103 {
0104     return QPointF(m_radius * cos(qDegreesToRadians(m_angleDegree)), //
0105                    m_radius * sin(qDegreesToRadians(m_angleDegree)));
0106 }
0107 
0108 /** @internal
0109  *
0110  * @brief Adds QDebug() support for data type
0111  * @ref PerceptualColor::PolarPointF
0112  *
0113  * @param dbg Existing debug object
0114  * @param value Value to stream into the debug object
0115  * @returns Debug object with value streamed in */
0116 QDebug operator<<(QDebug dbg, const PerceptualColor::PolarPointF value)
0117 {
0118     dbg.nospace() //
0119         << "PolarPointF(radius: " //
0120         << value.radius() //
0121         << ", angleDegree: " //
0122         << value.angleDegree() //
0123         << "°)";
0124     return dbg.maybeSpace();
0125 }
0126 
0127 static_assert(std::is_trivially_copyable_v<PolarPointF>);
0128 // static_assert(std::is_trivial_v<PolarPointF>);
0129 
0130 static_assert(std::is_standard_layout_v<PolarPointF>);
0131 
0132 static_assert(std::is_default_constructible_v<PolarPointF>);
0133 // static_assert(std::is_trivially_default_constructible_v<PolarPointF>);
0134 static_assert(std::is_nothrow_default_constructible_v<PolarPointF>);
0135 
0136 static_assert(std::is_copy_constructible_v<PolarPointF>);
0137 static_assert(std::is_trivially_copy_constructible_v<PolarPointF>);
0138 static_assert(std::is_nothrow_copy_constructible_v<PolarPointF>);
0139 
0140 static_assert(std::is_move_constructible_v<PolarPointF>);
0141 static_assert(std::is_trivially_move_constructible_v<PolarPointF>);
0142 static_assert(std::is_nothrow_move_constructible_v<PolarPointF>);
0143 
0144 } // namespace PerceptualColor