Cleaning up and documenting scmp code.

This commit is contained in:
Kyle Isom 2023-10-20 02:59:36 -07:00
parent 4eb4008130
commit b49caa3ec9
12 changed files with 343 additions and 108 deletions

View File

@ -1,35 +1,27 @@
# Use the latest 2.1 version of CircleCI pipeline process engine.
# See: https://circleci.com/docs/configuration-reference
version: 2.1 version: 2.1
# Define a job to be invoked later in a workflow.
# See: https://circleci.com/docs/configuration-reference/#jobs
jobs: jobs:
ctest: ctest:
# Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub.
# See: https://circleci.com/docs/configuration-reference/#executor-job
docker: docker:
- image: git.wntrmute.dev/sc/dev:alpine - image: git.wntrmute.dev/sc/dev:alpine
# Add steps to the job
# See: https://circleci.com/docs/configuration-reference/#steps
steps: steps:
- checkout - checkout
- run: - run:
name: Setup cmake build name: Setup cmake build
command: setup-cmake.sh command: setup-cmake.sh
static_analysis: static_analysis:
docker: docker:
- image: git.wntrmute.dev/sc/dev:alpine - image: git.wntrmute.dev/sc/dev:alpine
steps: steps:
- checkout - checkout
- run: - run:
- name: trunk check name: Trunk check
command: ./trunk check command: ./trunk check
# Orchestrate jobs using workflows
# See: https://circleci.com/docs/configuration-reference/#workflows
workflows: workflows:
ctest: ctest:
jobs: jobs:
- ctest - ctest
static_analysis:
jobs:
- static_analysis

View File

@ -46,6 +46,7 @@ set(HEADER_FILES
include/scsl/StringUtil.h include/scsl/StringUtil.h
include/scsl/TLV.h include/scsl/TLV.h
include/scmp/geom.h
include/scmp/scmp.h include/scmp/scmp.h
include/scmp/Math.h include/scmp/Math.h
include/scmp/Motion2D.h include/scmp/Motion2D.h
@ -116,6 +117,7 @@ generate_test(stringutil)
# math and physics # math and physics
generate_test(coord2d) generate_test(coord2d)
generate_test(madgwick) generate_test(madgwick)
generate_test(math)
generate_test(orientation) generate_test(orientation)
generate_test(quaternion) generate_test(quaternion)
generate_test(vector) generate_test(vector)

View File

@ -31,11 +31,10 @@
namespace scmp { namespace scmp {
/// MAX_RADIAN is a precomputed 2 * M_PI. and MIN_RADIAN is -2 * M_PI. /// MAX_RADIAN is a precomputed 2 * M_PI.
constexpr double MAX_RADIAN = 2 * M_PI; constexpr double MAX_RADIAN = 2 * M_PI;
constexpr double MIN_RADIAN = -2 * M_PI; constexpr double MIN_RADIAN = -2 * M_PI;
constexpr double POS_HALF_RADIAN = M_PI / 2; constexpr double PI_D = 3.141592653589793;
constexpr double NEG_HALF_RADIAN = -(M_PI / 2);
/// Roll m die of n sides, returning a vector of the dice. /// Roll m die of n sides, returning a vector of the dice.
@ -48,50 +47,65 @@ int DieTotal(int m, int n);
int BestDie(int k, int m, int n); int BestDie(int k, int m, int n);
/// Convert radians to degrees. /// \brief Convert radians to degrees.
/// @param rads the angle in radians ///
/// @return the angle in degrees. /// \param rads the angle in radians
/// \return the angle in degrees.
float RadiansToDegreesF(float rads); float RadiansToDegreesF(float rads);
/// Convert radians to degrees. /// \brief Convert radians to degrees.
/// @param rads the angle in radians ///
/// @return the angle in degrees. /// \param rads the angle in radians
/// \return the angle in degrees.
double RadiansToDegreesD(double rads); double RadiansToDegreesD(double rads);
/// Convert degrees to radians. /// \brief Convert degrees to radians.
/// @param degrees the angle in degrees ///
/// @return the angle in radians. /// \param degrees the angle in degrees
/// \return the angle in radians.
float DegreesToRadiansF(float degrees); float DegreesToRadiansF(float degrees);
/// Convert degrees to radians. /// \brief Convert degrees to radians.
/// @param degrees the angle in degrees ///
/// @return the angle in radians. /// \param degrees the angle in degrees
/// \return the angle in radians.
double DegreesToRadiansD(double degrees); double DegreesToRadiansD(double degrees);
/// RotateRadians rotates theta0 by theta1 radians, wrapping the result to /// \brief RotateRadians rotates theta0 by theta1 radians, wrapping
/// MIN_RADIAN <= result <= MAX_RADIAN. /// the result to MIN_RADIAN <= result <= MAX_RADIAN.
///
/// \param theta0
/// \param theta1
/// \return
double RotateRadians(double theta0, double theta1); double RotateRadians(double theta0, double theta1);
/// Get the default epsilon value. /// \brief Get the default epsilon value.
/// @param epsilon The variable to store the epsilon value in. ///
/// \param epsilon The variable to store the epsilon value in.
void DefaultEpsilon(double &epsilon); void DefaultEpsilon(double &epsilon);
/// Get the default epsilon value. /// Get the default epsilon value.
/// @param epsilon The variable to store the epsilon value in. ///
/// \param epsilon The variable to store the epsilon value in.
void DefaultEpsilon(float &epsilon); void DefaultEpsilon(float &epsilon);
/// Return whether the two values of type T are equal to within some tolerance. /// \brief Return whether the two values of type T are equal to within
/// @tparam T The type of value /// some tolerance.
/// @param a A value of type T used as the left-hand side of an equality check. ///
/// @param b A value of type T used as the right-hand side of an equality check. /// \tparam T The type of value
/// @param epsilon The tolerance value. /// \param a A value of type T used as the left-hand side of an
/// @return Whether the two values are "close enough" to be considered equal. /// equality check.
/// \param b A value of type T used as the right-hand side of an
/// equality check.
/// \param epsilon The tolerance value.
/// \return Whether the two values are "close enough" to be considered
/// equal.
template <typename T> template <typename T>
static T static T
WithinTolerance(T a, T b, T epsilon) WithinTolerance(T a, T b, T epsilon)
{ {
return std::abs(a - b) < epsilon; return std::abs(a - b) <= epsilon;
} }

View File

@ -55,7 +55,6 @@ public:
Madgwick() : deltaT(0.0), previousSensorFrame(), sensorFrame() Madgwick() : deltaT(0.0), previousSensorFrame(), sensorFrame()
{}; {};
/// \brief The Madgwick filter is initialised with a sensor frame. /// \brief The Madgwick filter is initialised with a sensor frame.
/// ///
/// \param sf A sensor frame; if zero, the sensor frame will be /// \param sf A sensor frame; if zero, the sensor frame will be
@ -67,7 +66,6 @@ public:
} }
} }
/// \brief Initialise the filter with a sensor frame quaternion. /// \brief Initialise the filter with a sensor frame quaternion.
/// ///
/// \param sf A quaternion representing the current Orientation. /// \param sf A quaternion representing the current Orientation.
@ -75,7 +73,6 @@ public:
deltaT(0.0), previousSensorFrame(), sensorFrame(sf) deltaT(0.0), previousSensorFrame(), sensorFrame(sf)
{}; {};
/// \brief Return the current orientation as measured by the /// \brief Return the current orientation as measured by the
/// filter. /// filter.
/// ///
@ -86,7 +83,6 @@ public:
return this->sensorFrame; return this->sensorFrame;
} }
/// \brief Return the filter's rate of angular change from a /// \brief Return the filter's rate of angular change from a
/// sensor frame. /// sensor frame.
/// ///
@ -152,7 +148,6 @@ public:
this->UpdateFrame(this->sensorFrame + q, delta); this->UpdateFrame(this->sensorFrame + q, delta);
} }
/// \brief Update the sensor frame with a gyroscope reading. /// \brief Update the sensor frame with a gyroscope reading.
/// ///
/// If no Δt is provided, the filter's default is used. /// If no Δt is provided, the filter's default is used.
@ -168,7 +163,6 @@ public:
this->UpdateAngularOrientation(gyro, this->deltaT); this->UpdateAngularOrientation(gyro, this->deltaT);
} }
/// \brief Retrieve a vector of the Euler angles in ZYX Orientation. /// \brief Retrieve a vector of the Euler angles in ZYX Orientation.
/// ///
/// \return A vector of Euler angles as <ψ, θ, ϕ>. /// \return A vector of Euler angles as <ψ, θ, ϕ>.

43
include/scmp/geom.h Normal file
View File

@ -0,0 +1,43 @@
///
/// \file include/scmp/geom.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-20
/// \brief 2D point and polar coordinate systems.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCSL_GEOM_H
#define SCSL_GEOM_H
#include <scmp/geom/Coord2D.h>
#include <scmp/geom/Orientation.h>
#include <scmp/geom/Quaternion.h>
#include <scmp/geom/Vector.h>
namespace scmp {
/// \brief Geometry-related code.
namespace geom {}
} // namespace scmp
#endif // SCSL_GEOM_H

View File

@ -1,29 +1,29 @@
/// coord2d.h defines 2D point and polar coordinate systems. ///
// /// \file include/scmp/geom/Coord2D.h
// Project: scccl /// \author K. Isom <kyle@imap.cc>
// File: include/math/coord2d.h /// \date 2017-06-05
// Author: Kyle Isom /// \brief 2D point and polar coordinate systems.
// Date: 2017-06-05 ///
// Namespace: math::geom /// Copyright 2017 K. Isom <kyle@imap.cc>
// ///
// coord2d.h defines 2D coordinate classes and functions. /// Permission to use, copy, modify, and/or distribute this software for
// /// any purpose with or without fee is hereby granted, provided that
// Copyright 2017 Kyle Isom <kyle@imap.cc> /// the above copyright notice and this permission notice appear in all /// copies.
// ///
// Licensed under the Apache License, Version 2.0 (the "License"); /// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
// you may not use this file except in compliance with the License. /// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
// You may obtain a copy of the License at /// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// /// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
// http://www.apache.org/licenses/LICENSE-2.0 /// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
// /// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
// Unless required by applicable law or agreed to in writing, software /// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// distributed under the License is distributed on an "AS IS" BASIS, /// PERFORMANCE OF THIS SOFTWARE.
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ///
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SCMATH_GEOM_COORD2D_H #ifndef SCMATH_GEOM_COORD2D_H
#define SCMATH_GEOM_COORD2D_H #define SCMATH_GEOM_COORD2D_H
#include <cmath> #include <cmath>
#include <ostream> #include <ostream>
#include <vector> #include <vector>
@ -34,20 +34,22 @@ namespace geom {
class Point2D; class Point2D;
class Polar2D; class Polar2D;
// Point2D is a logical grouping of a set of 2D cartesian coordinates. /// \brief Point2D is a logical grouping of a set of 2D cartesian
/// coordinates.
class Point2D { class Point2D {
public: public:
int x, y; int x, y;
// A Point2D can be initialised by setting its members to 0, by providing the // A Point2D can be initialised by setting its members to 0, by providing the
// x and y coordiantes, or through translation from a polar coordinate. // x and y coordiantes, or through translation from a polar coordinate.
Point2D() : x(0), y(0) {} Point2D();
Point2D(int _x, int _y) : x(_x), y(_y) {} Point2D(int _x, int _y);
Point2D(const Polar2D &); Point2D(const Polar2D &);
std::string ToString(void); std::string ToString();
void ToPolar(Polar2D &); void ToPolar(Polar2D &);
// Rotate rotates the point by theta radians. Alternatively, a rotation // Rotate rotates the point by theta radians. Alternatively, a rotation
@ -65,11 +67,15 @@ class Point2D {
// Distance returns the distance from this point to another. // Distance returns the distance from this point to another.
int Distance(const Point2D &other); int Distance(const Point2D &other);
Point2D operator+(const Point2D &rhs) const { return Point2D(x + rhs.x, y + rhs.y); } Point2D operator+(const Point2D &rhs) const
Point2D operator-(const Point2D &rhs) const { return Point2D(x - rhs.x, y - rhs.y); } { return Point2D(x + rhs.x, y + rhs.y); }
Point2D operator*(const int k) const { return Point2D(x * k, y * k); } Point2D operator-(const Point2D &rhs) const
{ return Point2D(x - rhs.x, y - rhs.y); }
Point2D operator*(const int k) const
{ return Point2D(x * k, y * k); }
bool operator==(const Point2D &rhs) const; bool operator==(const Point2D &rhs) const;
bool operator!=(const Point2D& rhs) const { return !(*this == rhs); } bool operator!=(const Point2D &rhs) const
{ return !(*this == rhs); }
friend std::ostream &operator<<(std::ostream &outs, const Point2D &pt); friend std::ostream &operator<<(std::ostream &outs, const Point2D &pt);
}; };
@ -82,8 +88,10 @@ class Polar2D {
// A Polar2D can be initialised as a zeroised polar coordinate, by specifying // A Polar2D can be initialised as a zeroised polar coordinate, by specifying
// the radius and angle directly, or via conversion from a Point2D. // the radius and angle directly, or via conversion from a Point2D.
Polar2D() : r(0.0), theta(0.0) {} Polar2D() : r(0.0), theta(0.0)
Polar2D(double _r, double _theta) : r(_r), theta(_theta) {} {}
Polar2D(double _r, double _theta) : r(_r), theta(_theta)
{}
Polar2D(const Point2D &); Polar2D(const Point2D &);
std::string ToString(); std::string ToString();
@ -98,7 +106,8 @@ class Polar2D {
void RotateAround(const Point2D &other, Point2D &result, double tjeta); void RotateAround(const Point2D &other, Point2D &result, double tjeta);
bool operator==(const Polar2D &) const; bool operator==(const Polar2D &) const;
bool operator!=(const Polar2D& rhs) const { return !(*this == rhs); } bool operator!=(const Polar2D &rhs) const
{ return !(*this == rhs); }
friend std::ostream &operator<<(std::ostream &, const Polar2D &); friend std::ostream &operator<<(std::ostream &, const Polar2D &);
}; };

View File

@ -25,9 +25,10 @@
/// \brief Shimmering Clarity Math & Physics toolkit. /// \brief Shimmering Clarity Math & Physics toolkit.
namespace scmp { ///
/// The Shimmering Clarity contains code related to math and physics,
} // namespace scmp /// particularly as relevant to game programming and robotics.
namespace scmp {}
#endif //SCSL_SCMP_H #endif //SCSL_SCMP_H

View File

@ -35,11 +35,17 @@ namespace sctest {
#define SCTEST_CHECK_FALSE(x) if ((x)) { return false; } #define SCTEST_CHECK_FALSE(x) if ((x)) { return false; }
#define SCTEST_CHECK_EQ(x, y) if ((x) != (y)) { return false; } #define SCTEST_CHECK_EQ(x, y) if ((x) != (y)) { return false; }
#define SCTEST_CHECK_NE(x, y) if ((x) == (y)) { return false; } #define SCTEST_CHECK_NE(x, y) if ((x) == (y)) { return false; }
#define SCTEST_CHECK_GEQ(x, y) if ((x) < (y)) { return false; }
#define SCTEST_CHECK_LEQ(x, y) if ((x) > (y)) { return false; }
#define SCTEST_CHECK_FEQ(x, y) { float eps; scmp::DefaultEpsilon(eps); if (!scmp::WithinTolerance((x), (y), eps)) { return false; }} #define SCTEST_CHECK_FEQ(x, y) { float eps; scmp::DefaultEpsilon(eps); if (!scmp::WithinTolerance((x), (y), eps)) { return false; }}
#define SCTEST_CHECK_DEQ(x, y) { double eps; scmp::DefaultEpsilon(eps); if (!scmp::WithinTolerance((x), (y), eps)) { return false; }} #define SCTEST_CHECK_DEQ(x, y) { double eps; scmp::DefaultEpsilon(eps); if (!scmp::WithinTolerance((x), (y), eps)) { return false; }}
#define SCTEST_CHECK_FEQ_EPS(x, y, eps) { if (!scmp::WithinTolerance<float>((x), (y), eps)) { return false; }} #define SCTEST_CHECK_FEQ_EPS(x, y, eps) { if (!scmp::WithinTolerance<float>((x), (y), (eps))) { return false; }}
#define SCTEST_CHECK_DEQ_EPS(x, y, eps) { if (!scmp::WithinTolerance<double>((x), (y), eps)) { return false; }} #define SCTEST_CHECK_FNE_EPS(x, y, eps) { if (scmp::WithinTolerance<float>((x), (y), (eps))) { return false; }}
#define SCTEST_CHECK_DEQ_EPS(x, y, eps) { if (!scmp::WithinTolerance<double>((x), (y), (eps))) { return false; }}
#define SCTEST_CHECK_DNE_EPS(x, y, eps) { if (scmp::WithinTolerance<double>((x), (y), (eps))) { return false; }}
} // namespace sctest } // namespace sctest

View File

@ -42,6 +42,11 @@ namespace geom {
// //
// Point2D // Point2D
Point2D::Point2D() : x(0), y(0) {}
Point2D::Point2D(int _x, int _y) : x(_x), y(_y) {}
Point2D::Point2D(const Polar2D &pol) Point2D::Point2D(const Polar2D &pol)
: x(std::rint(std::cos(pol.theta) * pol.r)), : x(std::rint(std::cos(pol.theta) * pol.r)),
y(std::rint(std::sin(pol.theta) * pol.r)) {} y(std::rint(std::sin(pol.theta) * pol.r)) {}

View File

@ -61,7 +61,7 @@ SimpleSuite::AddTest(std::string name, std::function<bool()> test)
void void
SimpleSuite::AddFailingTest(std::string name, std::function<bool()> test) SimpleSuite::AddFailingTest(std::string name, std::function<bool()> test)
{ {
const UnitTest test_case = {std::move(name), test, false}; const UnitTest test_case = {std::move(name), std::move(test), false};
tests.push_back(test_case); tests.push_back(test_case);
} }

View File

@ -24,6 +24,7 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <scsl/Flags.h>
#include <scmp/Math.h> #include <scmp/Math.h>
#include <scmp/geom/Coord2D.h> #include <scmp/geom/Coord2D.h>
#include <sctest/Checks.h> #include <sctest/Checks.h>
@ -222,9 +223,25 @@ geomRotatePointsAboutOrigin()
int int
main() main(int argc, char *argv[])
{ {
auto quiet = false;
auto flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
}
SimpleSuite suite; SimpleSuite suite;
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("geomValidateAngularRotation", geomValidateAngularRotation); suite.AddTest("geomValidateAngularRotation", geomValidateAngularRotation);
suite.AddTest("geomConversionIdentities", geomConversionIdentities); suite.AddTest("geomConversionIdentities", geomConversionIdentities);
suite.AddTest("geomVerifyBasicProperties", geomVerifyBasicProperties); suite.AddTest("geomVerifyBasicProperties", geomVerifyBasicProperties);
@ -232,8 +249,8 @@ main()
suite.AddTest("geomRotatePoint2D", geomRotatePoint2D); suite.AddTest("geomRotatePoint2D", geomRotatePoint2D);
suite.AddTest("geomRotatePointsAboutOrigin", geomRotatePointsAboutOrigin); suite.AddTest("geomRotatePointsAboutOrigin", geomRotatePointsAboutOrigin);
delete flags;
auto result = suite.Run(); auto result = suite.Run();
std::cout << suite << "\n"; std::cout << suite.GetReport() << "\n";
return result ? 0 : 1; return result ? 0 : 1;
} }

152
test/math.cc Normal file
View File

@ -0,0 +1,152 @@
///
/// \file test/math.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-20
/// \brief Unit tests for math functions.
///
/// Arena defines a memory management backend for pre-allocating memory.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <iostream>
#include <scsl/Flags.h>
#include <scmp/Math.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
namespace {
bool
BestDie()
{
// Theoretically, this could fail. The odds of that happening
// with 100 die and a proper RNG is 0.99999998792533,
// practically 100%. At 1000 die, it's virtually guaranteed.
auto n = scmp::BestDie(1000, 6, 1);
SCTEST_CHECK_EQ(n, 6);
return true;
}
bool
DieTotal()
{
auto n = scmp::DieTotal(100, 6);
SCTEST_CHECK_GEQ(n, 100);
SCTEST_CHECK_LEQ(n, 600);
return true;
}
bool
WithinToleranceFloat()
{
float x = 0.1235;
float y = 0.1236;
float eps = 0.0;
float expected = 0.1234;
scmp::DefaultEpsilon(eps);
SCTEST_CHECK_FEQ_EPS(x, expected, eps);
SCTEST_CHECK_FNE_EPS(y, expected, eps);
return true;
}
bool
WithinToleranceDouble()
{
double x = 0.12348;
double y = 0.1236;
double eps = 0.0;
double expected = 0.12345;
scmp::DefaultEpsilon(eps);
SCTEST_CHECK_DEQ_EPS(x, expected, eps);
SCTEST_CHECK_DNE_EPS(y, expected, eps);
return true;
}
bool
RotateRadians()
{
double theta0 = 0.0;
double theta1 = scmp::PI_D;
auto rotated = scmp::RotateRadians(theta0, theta1);
SCTEST_CHECK_DEQ(rotated, theta1);
rotated = scmp::RotateRadians(rotated, theta1);
SCTEST_CHECK_DEQ(rotated, theta0);
theta1 = scmp::PI_D * 3 / 2;
rotated = scmp::RotateRadians(theta0, theta1);
SCTEST_CHECK_DEQ(rotated, -scmp::PI_D / 2);
rotated = scmp::RotateRadians(rotated, theta1);
SCTEST_CHECK_DEQ(rotated, scmp::PI_D);
return true;
}
} // anonymous namespace
int
main(int argc, char *argv[])
{
auto quiet = false;
auto flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
}
sctest::SimpleSuite suite;
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("BestDie", BestDie);
suite.AddTest("DieTotal", DieTotal);
suite.AddTest("WithinToleranceFloat", WithinToleranceFloat);
suite.AddTest("WithinToleranceDouble", WithinToleranceDouble);
suite.AddTest("RotateRadians", RotateRadians);
delete flags;
auto result = suite.Run();
std::cout << suite.GetReport() << "\n";
return result ? 0 : 1;
}