diff --git a/CMakeLists.txt b/CMakeLists.txt index d9e756a..8a70c35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,28 +33,36 @@ endif () add_compile_definitions(SCSL_DESKTOP_BUILD) add_compile_definitions(SCSL_VERSION=${PROJECT_VERSION}) -set(HEADER_FILES scsl.h - Arena.h - Buffer.h - Commander.h - Dictionary.h - Exceptions.h - Flag.h - StringUtil.h - TLV.h - Test.h +set(HEADER_FILES + include/scsl/scsl.h + include/scsl/Arena.h + include/scsl/Buffer.h + include/scsl/Commander.h + include/scsl/Dictionary.h + include/scsl/Exceptions.h + include/scsl/Flag.h + include/scsl/StringUtil.h + include/scsl/TLV.h + + include/sctest/Assert.h + include/sctest/Report.h ) +include_directories(include) + set(SOURCE_FILES - Arena.cc - Buffer.cc - Commander.cc - Dictionary.cc - Exceptions.cc - Flag.cc - StringUtil.cc - TLV.cc - Test.cc + src/sl/Arena.cc + src/sl/Buffer.cc + src/sl/Commander.cc + src/sl/Dictionary.cc + src/sl/Exceptions.cc + src/sl/Flag.cc + src/sl/StringUtil.cc + src/sl/TLV.cc + + src/test/Assert.cc + src/test/Report.cc + src/test/SimpleSuite.cc ) if (APPLE) @@ -67,31 +75,27 @@ add_library(scsl ${SOURCE_FILES} ${HEADER_FILES}) endif() -add_executable(phonebook phonebook.cc) +add_executable(phonebook src/bin/phonebook.cc) target_link_libraries(phonebook scsl) include(CTest) enable_testing() -add_executable(buffer_test bufferTest.cc) -target_link_libraries(buffer_test scsl) -add_test(bufferTest buffer_test) +set(TEST_SOURCES) +macro(generate_test name) + add_executable(test_${name} test/${name}.cc ${TEST_SOURCES} ${ARGN}) + target_link_libraries(test_${name} ${PROJECT_NAME}) + target_include_directories(test_${name} PRIVATE test) + add_test(test_${name} test_${name}) +endmacro() -add_executable(tlv_test tlvTest.cc) -target_link_libraries(tlv_test scsl) -add_test(tlvTest tlv_test) +generate_test(buffer) +generate_test(tlv) +generate_test(dictionary) +generate_test(flag) +generate_test(stringutil) -add_executable(dictionary_test dictionaryTest.cc) -target_link_libraries(dictionary_test scsl) -add_test(dictionaryTest dictionary_test) - -add_executable(flag_test flagTest.cc) -target_link_libraries(flag_test scsl) -add_test(flagTest flag_test) - -add_executable(stringutil_test stringutil_test.cc) -target_link_libraries(stringutil_test scsl) -add_test(stringutilTest stringutil_test) +generate_test(simple_suite_example) include(CMakePackageConfigHelpers) write_basic_package_version_file( diff --git a/include/scmp/geom/coord2d.h b/include/scmp/geom/coord2d.h new file mode 100755 index 0000000..1b2e77f --- /dev/null +++ b/include/scmp/geom/coord2d.h @@ -0,0 +1,108 @@ +/// coord2d.h defines 2D point and polar coordinate systems. +// +// Project: scccl +// File: include/math/coord2d.h +// Author: Kyle Isom +// Date: 2017-06-05 +// Namespace: math::geom +// +// coord2d.h defines 2D coordinate classes and functions. +// +// Copyright 2017 Kyle Isom +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 +#define SCMATH_GEOM_COORD2D_H + +#include +#include +#include + + +namespace scmath { +namespace geom { + + +class Point2D; +class Polar2D; + +// Point2D is a logical grouping of a set of 2D cartesian coordinates. +class Point2D { + public: + int x, y; + + // 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. + Point2D() : x(0), y(0) {} + Point2D(int _x, int _y) : x(_x), y(_y) {} + Point2D(const Polar2D&); + + std::string ToString(void); + void ToPolar(Polar2D&); + + // Rotate rotates the point by theta radians. Alternatively, a rotation + // can use this point as the centre, with a polar coordinate and a rotation + // amount (in radians). The latter is used to specify a central point + // of rotation with vertices specified as polar coordinates from the centre. + // Both forms take a reference to a Point2D to store the rotated point. + void Rotate(Point2D& rotated, double theta); + std::vector Rotate(std::vector, double); + + // Translate adds this point to the first argument, storing the result in the + // second argument. + void Translate(const Point2D& other, Point2D& translated); + + // Distance returns the distance from this point to another. + 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 { 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 { return !(*this == rhs); } + friend std::ostream& operator<<(std::ostream& outs, const Point2D& pt); +}; + +// A Polar2D is a 2D polar coordinate, specified in terms of the radius from +// some origin and the angle from the positive X axis of a cartesian coordinate +// system. +class Polar2D { + public: + double r, theta; + + // A Polar2D can be initialised as a zeroised polar coordinate, by specifying + // the radius and angle directly, or via conversion from a Point2D. + Polar2D() : r(0.0), theta(0.0) {} + Polar2D(double _r, double _theta) : r(_r), theta(_theta) {} + Polar2D(const Point2D&); + + std::string ToString(); + void ToPoint(Point2D&); + + // Rotate rotates the polar coordinate by the number of radians, storing the result + // in the Polar2D argument. + void Rotate(Polar2D&, double); + + // RotateAround rotates this point about by theta radians, storing the rotated point + // in result. + void RotateAround(const Point2D& other, Point2D& result, double tjeta); + + bool operator==(const Polar2D&) const; + bool operator!=(const Polar2D& rhs) const { return !(*this == rhs); } + friend std::ostream& operator<<(std::ostream&, const Polar2D&); +}; + + +} // end namespace geom +} // end namespace math +#endif \ No newline at end of file diff --git a/include/scmp/geom/orientation.h b/include/scmp/geom/orientation.h new file mode 100644 index 0000000..53284ce --- /dev/null +++ b/include/scmp/geom/orientation.h @@ -0,0 +1,88 @@ +/** + * orientation.h concerns itself with computing the orientation of some + * vector with respect to a reference plane that is assumed to be the + * of the Earth. + */ + + +#ifndef SCMATH_GEOM_ORIENTATION_H +#define SCMATH_GEOM_ORIENTATION_H + + +namespace scmath { +namespace geom { + + +/// \defgroup basis Basis vector indices. +/// The following constants are provided as a convenience for indexing two- +/// and three-dimensional vectors. + +/// \ingroup basis +/// Convenience constant for the x index. +constexpr uint8_t Basis_x = 0; + +/// \ingroup basis +/// Convenience constant for the y index. +constexpr uint8_t Basis_y = 1; + +/// \ingroup basis +/// Convenience constant for the z index. +constexpr uint8_t Basis_z = 2; + + +/// @brief Basis2d provides basis vectors for Vector2ds. +static const Vector2d Basis2d[] = { + Vector2d{1, 0}, + Vector2d{0, 1}, +}; + + +/// @brief Basis2d provides basis vectors for Vector2fs. +static const Vector2f Basis2f[] = { + Vector2f{1, 0}, + Vector2f{0, 1}, +}; + + +/// @brief Basis2d provides basis vectors for Vector3ds. +static const Vector3d Basis3d[] = { + Vector3d{1, 0, 0}, + Vector3d{0, 1, 0}, + Vector3d{0, 0, 1}, +}; + + +/// @brief Basis2d provides basis vectors for Vector3fs. +static const Vector3f Basis3f[] = { + Vector3f{1, 0, 0}, + Vector3f{0, 1, 0}, + Vector3f{0, 0, 1}, +}; + + +/// Heading2f returns a compass heading for a Vector2f. +/// @param vec A vector orientation. +/// @return The compass heading of the vector in radians. +float Heading2f(Vector2f vec); + +/// Heading2d returns a compass heading for a Vector2d. +/// @param vec A vector orientation. +/// @return The compass heading of the vector in radians. +double Heading2d(Vector2d vec); + +/// Heading3f returns a compass heading for a Vector2f. +/// @param vec A vector orientation. +/// @return The compass heading of the vector in radians. +float Heading3f(Vector3f vec); + +/// Heading3d returns a compass heading for a Vector2f. +/// @param vec A vector orientation. +/// @return The compass heading of the vector in radians. +double Heading3d(Vector3d vec); + + +} // namespace geom +} // namespace math + + +#endif // __WRMATH_ORIENTATION_H diff --git a/include/scmp/geom/quaternion.h b/include/scmp/geom/quaternion.h new file mode 100644 index 0000000..423a0ab --- /dev/null +++ b/include/scmp/geom/quaternion.h @@ -0,0 +1,520 @@ +/// quaternion.h contains an implementation of quaternions suitable +/// for navigation in R3. +#ifndef SCMATH_QUATERNION_H +#define SCMATH_QUATERNION_H + + +#include +#include +#include +#include +#include + +#include +#include + +/// math contains the shimmering clarity math library. +namespace scmath { +/// geom contains geometric classes and functions. +namespace geom { + + +/// @brief Quaternions provide a representation of orientation and rotations +/// in three dimensions. +/// +/// Quaternions encode rotations in three-dimensional space. While technically +/// a quaternion is comprised of a real element and a complex vector<3>, for +/// the purposes of this library, it is modeled as a floating point 4D vector +/// of the form , where x, y, and z represent an axis of rotation in +/// R3 and w the angle, in radians, of the rotation about that axis. Where Euler +/// angles are concerned, the ZYX (or yaw, pitch, roll) sequence is used. +/// +/// For information on the underlying vector type, see the documentation for +/// wr::geom::Vector. +/// +/// The constructors are primarily intended for intended operations; in practice, +/// the quaternionf() and quaterniond() functions are more useful for constructing +/// quaternions from vectors and angles. +/// +/// Like vectors, quaternions carry an internal tolerance value ε that is used for +/// floating point comparisons. The math namespace contains the default values +/// used for this; generally, a tolerance of 0.0001 is considered appropriate for +/// the uses of this library. The tolerance can be explicitly set with the +/// setEpsilon method. +template +class Quaternion { +public: + /// The default Quaternion constructor returns an identity quaternion. + Quaternion() : v(Vector{0.0, 0.0, 0.0}), w(1.0) + { + scmath::DefaultEpsilon(this->eps); + v.setEpsilon(this->eps); + }; + + + /// A Quaternion may be initialised with a Vector axis of rotation + /// and an angle of rotation. This doesn't do the angle transforms to simplify + /// internal operations. + /// + /// @param _axis A three-dimensional vector of the same type as the Quaternion. + /// @param _angle The angle of rotation about the axis of rotation. + Quaternion(Vector _axis, T _angle) : v(_axis), w(_angle) + { + this->constrainAngle(); + scmath::DefaultEpsilon(this->eps); + v.setEpsilon(this->eps); + }; + + + /// A Quaternion may be initialised with a Vector comprised of + /// the axis of rotation followed by the angle of rotation. + /// + /// @param vector A vector in the form . + Quaternion(Vector vector) : + v(Vector{vector[1], vector[2], vector[3]}), + w(vector[0]) + { + this->constrainAngle(); + scmath::DefaultEpsilon(this->eps); + v.setEpsilon(this->eps); + } + + + /// A Quaternion may be constructed with an initializer list of + /// type T, which must have exactly N elements. + /// + /// @param ilst An initial set of values in the form . + Quaternion(std::initializer_list ilst) + { + auto it = ilst.begin(); + + this->v = Vector{it[1], it[2], it[3]}; + this->w = it[0]; + + this->constrainAngle(); + scmath::DefaultEpsilon(this->eps); + v.setEpsilon(this->eps); + } + + + /// Set the comparison tolerance for this quaternion. + /// + /// @param epsilon A tolerance value. + void + setEpsilon(T epsilon) + { + this->eps = epsilon; + this->v.setEpsilon(epsilon); + } + + + /// Return the axis of rotation of this quaternion. + /// + /// @return The axis of rotation of this quaternion. + Vector + axis() const + { + return this->v; + } + + + /// Return the angle of rotation of this quaternion. + /// + /// @return the angle of rotation of this quaternion. + T + angle() const + { + return this->w; + } + + + /// Compute the dot product of two quaternions. + /// + /// \param other Another quaternion. + /// \return The dot product between the two quaternions. + T + dot(const Quaternion &other) const + { + double innerProduct = this->v[0] * other.v[0]; + + innerProduct += (this->v[1] * other.v[1]); + innerProduct += (this->v[2] * other.v[2]); + innerProduct += (this->w * other.w); + return innerProduct; + } + + + /// Compute the norm of a quaternion. Treating the Quaternion as a + /// Vector, it's the same as computing the magnitude. + /// + /// @return A non-negative real number. + T + norm() const + { + T n = 0; + + n += (this->v[0] * this->v[0]); + n += (this->v[1] * this->v[1]); + n += (this->v[2] * this->v[2]); + n += (this->w * this->w); + + return std::sqrt(n); + } + + + /// Return the unit quaternion. + /// + /// \return The unit quaternion. + Quaternion + unitQuaternion() + { + return *this / this->norm(); + } + + /// Compute the conjugate of a quaternion. + /// + /// @return The conjugate of this quaternion. + Quaternion + conjugate() const + { + return Quaternion(Vector{this->w, -this->v[0], -this->v[1], -this->v[2]}); + } + + + /// Compute the inverse of a quaternion. + /// + /// @return The inverse of this quaternion. + Quaternion + inverse() const + { + T _norm = this->norm(); + + return this->conjugate() / (_norm * _norm); + } + + + /// Determine whether this is an identity quaternion. + /// + /// \return true if this is an identity quaternion. + bool + isIdentity() const { + return this->v.isZero() && + scmath::WithinTolerance(this->w, (T)1.0, this->eps); + } + + + /// Determine whether this is a unit quaternion. + /// + /// @return true if this is a unit quaternion. + bool + isUnitQuaternion() const + { + return scmath::WithinTolerance(this->norm(), (T) 1.0, this->eps); + } + + + /// Return the quaternion as a Vector, with the axis of rotation + /// followed by the angle of rotation. + /// + /// @return A vector representation of the quaternion. + Vector + asVector() const + { + return Vector{this->w, this->v[0], this->v[1], this->v[2]}; + } + + + /// Rotate vector vr about this quaternion. + /// + /// @param vr The vector to be rotated. + /// @return The rotated vector. + Vector + rotate(Vector vr) const + { + return (this->conjugate() * vr * (*this)).axis(); + } + + + /// Return the Euler angles for this quaternion as a vector of + /// . Users of this function should watch out + /// for gimbal lock. + /// + /// @return A vector containing + Vector + euler() const + { + T yaw, pitch, roll; + T a = this->w, a2 = a * a; + T b = this->v[0], b2 = b * b; + T c = this->v[1], c2 = c * c; + T d = this->v[2], d2 = d * d; + + yaw = std::atan2(2 * ((a * b) + (c * d)), a2 - b2 - c2 + d2); + pitch = std::asin(2 * ((b * d) - (a * c))); + roll = std::atan2(2 * ((a * d) + (b * c)), a2 + b2 - c2 - d2); + + return Vector{yaw, pitch, roll}; + } + + + /// Perform quaternion addition with another quaternion. + /// + /// @param other The quaternion to be added with this one. + /// @return The result of adding the two quaternions together. + Quaternion + operator+(const Quaternion &other) const + { + return Quaternion(this->v + other.v, this->w + other.w); + } + + + /// Perform quaternion subtraction with another quaternion. + /// + /// @param other The quaternion to be subtracted from this one. + /// @return The result of subtracting the other quaternion from this one. + Quaternion + operator-(const Quaternion &other) const + { + return Quaternion(this->v - other.v, this->w - other.w); + } + + + /// Perform scalar multiplication. + /// + /// @param k The scaling value. + /// @return A scaled quaternion. + Quaternion + operator*(const T k) const + { + return Quaternion(this->v * k, this->w * k); + } + + + /// Perform scalar division. + /// + /// @param k The scalar divisor. + /// @return A scaled quaternion. + Quaternion + operator/(const T k) const + { + return Quaternion(this->v / k, this->w / k); + } + + + /// Perform quaternion Hamilton multiplication with a three- + /// dimensional vector; this is done by treating the vector + /// as a pure quaternion (e.g. with an angle of rotation of 0). + /// + /// @param vector The vector to multiply with this quaternion. + /// @return The Hamilton product of the quaternion and vector. + Quaternion + operator*(const Vector &vector) const + { + return Quaternion(vector * this->w + this->v.cross(vector), + (T) 0.0); + } + + + /// Perform quaternion Hamilton multiplication. + /// + /// @param other The other quaternion to multiply with this one. + /// @result The Hamilton product of the two quaternions. + Quaternion + operator*(const Quaternion &other) const + { + T angle = (this->w * other.w) - + (this->v * other.v); + Vector axis = (other.v * this->w) + + (this->v * other.w) + + (this->v.cross(other.v)); + return Quaternion(axis, angle); + } + + + /// Perform quaternion equality checking. + /// @param other The quaternion to check equality against. + /// @return True if the two quaternions are equal within their tolerance. + bool + operator==(const Quaternion &other) const + { + return (this->v == other.v) && + (scmath::WithinTolerance(this->w, other.w, this->eps)); + } + + + /// Perform quaternion inequality checking. + /// + /// @param other The quaternion to check inequality against. + /// @return True if the two quaternions are unequal within their tolerance. + bool + operator!=(const Quaternion &other) const + { + return !(*this == other); + } + + + /// Support stream output of a quaternion in the form `a + `. + /// \todo improve the formatting. + /// + /// @param outs An output stream + /// @param q A quaternion + /// @return The output stream + friend std::ostream & + operator<<(std::ostream &outs, const Quaternion &q) + { + outs << q.w << " + " << q.v; + return outs; + } + +private: + static constexpr T minRotation = -4 * M_PI; + static constexpr T maxRotation = 4 * M_PI; + + Vector v; // axis of rotation + T w; // angle of rotation + T eps; + + void + constrainAngle() + { + if (this->w < 0.0) { + this->w = std::fmod(this->w, this->minRotation); + } + else { + this->w = std::fmod(this->w, this->maxRotation); + } + } +}; + + +/// +/// \defgroup quaternion_aliases Quaternion type aliases. +/// Type aliases are provided for float and double quaternions. +/// + +/// \ingroup quaternion_aliases +/// Type alias for a float Quaternion. +typedef Quaternion Quaternionf; + +/// \ingroup quaternion_aliases +/// Type alias for a double Quaternion. +typedef Quaternion Quaterniond; + + +/// Return a float quaternion scaled appropriately from a vector and angle, +/// e.g. angle = cos(angle / 2), axis.unitVector() * sin(angle / 2). +/// +/// @param axis The axis of rotation. +/// @param angle The angle of rotation. +/// @return A quaternion. +/// @relatesalso Quaternion +Quaternionf quaternionf(Vector3f axis, float angle); + + +/// Return a double quaternion scaled appropriately from a vector and angle, +/// e.g. angle = cos(angle / 2), axis.unitVector() * sin(angle / 2). +/// +/// @param axis The axis of rotation. +/// @param angle The angle of rotation. +/// @return A quaternion. +/// @relatesalso Quaternion +Quaterniond quaterniond(Vector3d axis, double angle); + + +/// Return a double quaternion scaled appropriately from a vector and angle, +/// e.g. angle = cos(angle / 2), axis.unitVector() * sin(angle / 2). +/// +/// @param axis The axis of rotation. +/// @param angle The angle of rotation. +/// @return A quaternion. +/// @relatesalso Quaternion +template +Quaternion +quaternion(Vector axis, T angle) +{ + return Quaternion(axis.unitVector() * std::sin(angle / (T)2.0), + std::cos(angle / (T)2.0)); +} + + +/// Given a vector of Euler angles in ZYX sequence (e.g. yaw, pitch, roll), +/// return a quaternion. +/// +/// @param euler A vector Euler angle in ZYX sequence. +/// @return A Quaternion representation of the orientation represented +/// by the Euler angles. +/// @relatesalso Quaternion +Quaternionf quaternionf_from_euler(Vector3f euler); + + +/// Given a vector of Euler angles in ZYX sequence (e.g. yaw, pitch, roll), +/// return a quaternion. +/// +/// @param euler A vector Euler angle in ZYX sequence. +/// @return A Quaternion representation of the orientation represented +/// by the Euler angles. +/// @relatesalso Quaternion +Quaterniond quaterniond_from_euler(Vector3d euler); + + +/// LERP computes the linear interpolation of two quaternions at some +/// fraction of the distance between them. +/// +/// \tparam T +/// \param p The starting quaternion. +/// \param q The ending quaternion. +/// \param t The fraction of the distance between the two quaternions to +/// interpolate. +/// \return A Quaternion representing the linear interpolation of the +/// two quaternions. +template +Quaternion +LERP(Quaternion p, Quaternion q, T t) +{ + return (p + (q - p) * t).unitQuaternion(); +} + + +/// ShortestSLERP computes the shortest distance spherical linear +/// interpolation between two quaternions at some fraction of the +/// distance between them. +/// +/// \tparam T +/// \param p The starting quaternion. +/// \param q The ending quaternion.Short +/// \param t The fraction of the distance between the two quaternions +/// to interpolate. +/// \return A Quaternion representing the shortest path between two +/// quaternions. +template +Quaternion +ShortestSLERP(Quaternion p, Quaternion q, T t) +{ + assert(p.isUnitQuaternion()); + assert(q.isUnitQuaternion()); + + T dp = p.dot(q); + T sign = dp < 0.0 ? -1.0 : 1.0; + T omega = std::acos(dp * sign); + T sin_omega = std::sin(omega); // Compute once. + + if (dp > 0.99999) { + return LERP(p, q * sign, t); + } + + return (p * std::sin((1.0 - t) * omega) / sin_omega) + + (q * sign * std::sin(omega*t) / sin_omega); +} + + +/// Run a quick self test to exercise basic functionality of the Quaternion +/// class to verify correct operation. Note that if \#NDEBUG is defined, the +/// self test is disabled. +void Quaternion_SelfTest(); + + +} // namespace geom +} // namespace wr + + +#endif // WRMATH_QUATERNION_H diff --git a/include/scmp/geom/vector.h b/include/scmp/geom/vector.h new file mode 100644 index 0000000..be699a1 --- /dev/null +++ b/include/scmp/geom/vector.h @@ -0,0 +1,422 @@ +// +// Project: scccl +// File: include/math/vectors.h +// Author: Kyle Isom +// Date: 2017-06-05 +// Namespace: math::vectors. +// +// vectors.h defines the Vector2D class and associated functions in the +// namespace math::vectors. +// +// Copyright 2017 Kyle Isom +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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_VECTORS_H +#define SCMATH_VECTORS_H + +#include +#include +#include +#include +#include +#include + +#include + + +// This implementation is essentially a C++ translation of a Python library +// I wrote for Coursera's "Linear Algebra for Machine Learning" course. Many +// of the test vectors come from quiz questions in the class. + + +namespace scmath { +namespace geom { + + +/// @brief Vectors represent a direction and magnitude. +/// +/// Vector provides a standard interface for dimensionless fixed-size +/// vectors. Once instantiated, they cannot be modified. +/// +/// Note that while the class is templated, it's intended to be used with +/// floating-point types. +/// +/// Vectors can be indexed like arrays, and they contain an epsilon value +/// that defines a tolerance for equality. +template +class Vector { +public: + /// The default constructor creates a unit vector for a given type + /// and size. + Vector() + { + T unitLength = (T)1.0 / std::sqrt(N); + for (size_t i = 0; i < N; i++) { + this->arr[i] = unitLength; + } + + scmath::DefaultEpsilon(this->epsilon); + } + + + /// If given an initializer_list, the vector is created with + /// those values. There must be exactly N elements in the list. + /// @param ilst An intializer list with N elements of type T. + Vector(std::initializer_list ilst) + { + assert(ilst.size() == N); + + scmath::DefaultEpsilon(this->epsilon); + std::copy(ilst.begin(), ilst.end(), this->arr.begin()); + } + + + /// Compute the length of the vector. + /// @return The length of the vector. + T magnitude() const { + T result = 0; + + for (size_t i = 0; i < N; i++) { + result += (this->arr[i] * this->arr[i]); + } + return std::sqrt(result); + } + + + /// Set the tolerance for equality checks. At a minimum, this allows + /// for systemic errors in floating math arithmetic. + /// @param eps is the maximum difference between this vector and + /// another. + void + setEpsilon(T eps) + { + this->epsilon = eps; + } + + + /// Determine whether this is a zero vector. + /// @return true if the vector is zero. + bool + isZero() const + { + for (size_t i = 0; i < N; i++) { + if (!scmath::WithinTolerance(this->arr[i], (T)0.0, this->epsilon)) { + return false; + } + } + return true; + } + + + /// Obtain the unit vector for this vector. + /// @return The unit vector + Vector + unitVector() const + { + return *this / this->magnitude(); + } + + + /// Determine if this is a unit vector, e.g. if its length is 1. + /// @return true if the vector is a unit vector. + bool + isUnitVector() const + { + return scmath::WithinTolerance(this->magnitude(), (T)1.0, this->epsilon); + } + + + /// Compute the angle between two other vectors. + /// @param other Another vector. + /// @return The angle in radians between the two vectors. + T + angle(const Vector &other) const + { + Vector unitA = this->unitVector(); + Vector unitB = other.unitVector(); + + // Can't compute angles with a zero vector. + assert(!this->isZero()); + assert(!other.isZero()); + return std::acos(unitA * unitB); + } + + + /// Determine whether two vectors are parallel. + /// @param other Another vector + /// @return True if the angle between the vectors is zero. + bool + isParallel(const Vector &other) const + { + if (this->isZero() || other.isZero()) { + return true; + } + + T angle = this->angle(other); + if (scmath::WithinTolerance(angle, (T)0.0, this->epsilon)) { + return true; + } + + return false; + } + + + /// Determine if two vectors are orthogonal or perpendicular to each + /// other. + /// @param other Another vector + /// @return True if the two vectors are orthogonal. + bool + isOrthogonal(const Vector &other) const + { + if (this->isZero() || other.isZero()) { + return true; + } + + return scmath::WithinTolerance(*this * other, (T)0.0, this->epsilon); + } + + + /// Project this vector onto some basis vector. + /// @param basis The basis vector to be projected onto. + /// @return A vector that is the projection of this onto the basis + /// vector. + Vector + projectParallel(const Vector &basis) const + { + Vector unit_basis = basis.unitVector(); + + return unit_basis * (*this * unit_basis); + } + + + /// Project this vector perpendicularly onto some basis vector. + /// This is also called the rejection of the vector. + /// @param basis The basis vector to be projected onto. + /// @return A vector that is the orthogonal projection of this onto + /// the basis vector. + Vector + projectOrthogonal(const Vector &basis) + { + Vector spar = this->projectParallel(basis); + return *this - spar; + } + + + /// Compute the cross product of two vectors. This is only defined + /// over three-dimensional vectors. + /// @param other Another 3D vector. + /// @return The cross product vector. + Vector + cross(const Vector &other) const + { + assert(N == 3); + return Vector { + (this->arr[1] * other.arr[2]) - (other.arr[1] * this->arr[2]), + -((this->arr[0] * other.arr[2]) - (other.arr[0] * this->arr[2])), + (this->arr[0] * other.arr[1]) - (other.arr[0] * this->arr[1]) + }; + } + + + /// Perform vector addition with another vector. + /// @param other The vector to be added. + /// @return A new vector that is the result of adding this and the + /// other vector. + Vector + operator+(const Vector &other) const + { + Vector vec; + + for (size_t i = 0; i < N; i++) { + vec.arr[i] = this->arr[i] + other.arr[i]; + } + + return vec; + } + + + /// Perform vector subtraction with another vector. + /// @param other The vector to be subtracted from this vector. + /// @return A new vector that is the result of subtracting the + /// other vector from this one. + Vector + operator-(const Vector &other) const + { + Vector vec; + + for (size_t i = 0; i < N; i++) { + vec.arr[i] = this->arr[i] - other.arr[i]; + } + + return vec; + } + + + /// Perform scalar multiplication of this vector by some scale factor. + /// @param k The scaling value. + /// @return A new vector that is this vector scaled by k. + Vector + operator*(const T k) const + { + Vector vec; + + for (size_t i = 0; i < N; i++) { + vec.arr[i] = this->arr[i] * k; + } + + return vec; + } + + + /// Perform scalar division of this vector by some scale factor. + /// @param k The scaling value + /// @return A new vector that is this vector scaled by 1/k. + Vector + operator/(const T k) const + { + Vector vec; + + for (size_t i = 0; i < N; i++) { + vec.arr[i] = this->arr[i] / k; + } + + return vec; + } + + + /// Compute the dot product between two vectors. + /// @param other The other vector. + /// @return A scalar value that is the dot product of the two vectors. + T + operator*(const Vector &other) const + { + T result = 0; + + for (size_t i = 0; i < N; i++) { + result += (this->arr[i] * other.arr[i]); + } + + return result; + } + + + /// Compare two vectors for equality. + /// @param other The other vector. + /// @return Return true if all the components of both vectors are + /// within the tolerance value. + bool + operator==(const Vector &other) const + { + for (size_t i = 0; iarr[i], other.arr[i], this->epsilon)) { + return false; + } + } + return true; + } + + + /// Compare two vectors for inequality. + /// @param other The other vector. + /// @return Return true if any of the components of both vectors are + /// not within the tolerance value. + bool + operator!=(const Vector &other) const + { + return !(*this == other); + } + + + /// Support array indexing into vector. + /// + /// Note that the values of the vector cannot be modified. Instead, + /// it's required to do something like the following: + /// + /// ``` + /// Vector3d a {1.0, 2.0, 3.0}; + /// Vector3d b {a[0], a[1]*2.0, a[2]}; + /// ``` + /// + /// @param i The component index. + /// @return The value of the vector component at i. + const T& + operator[](size_t i) const + { + return this->arr[i]; + } + + + /// Support outputting vectors in the form "". + /// @param outs An output stream. + /// @param vec The vector to be formatted. + /// @return The output stream. + friend std::ostream& + operator<<(std::ostream& outs, const Vector& vec) + { + outs << "<"; + for (size_t i = 0; i < N; i++) { + outs << vec.arr[i]; + if (i < (N-1)) { + outs << ", "; + } + } + outs << ">"; + return outs; + } + +private: + static const size_t dim = N; + T epsilon; + std::array arr; +}; + +/// +/// \defgroup vector_aliases Vector type aliases. +/// + +/// \ingroup vector_aliases +/// A number of shorthand aliases for vectors are provided. They follow +/// the form of VectorNt, where N is the dimension and t is the type. +/// For example, a 2D float vector is Vector2f. + +/// \ingroup vector_aliases +/// @brief Type alias for a two-dimensional float vector. +typedef Vector Vector2f; + +/// \ingroup vector_aliases +/// @brief Type alias for a three-dimensional float vector. +typedef Vector Vector3f; + +/// \ingroup vector_aliases +/// @brief Type alias for a four-dimensional float vector. +typedef Vector Vector4f; + +/// \ingroup vector_aliases +/// @brief Type alias for a two-dimensional double vector. +typedef Vector Vector2d; + +/// \ingroup vector_aliases +/// @brief Type alias for a three-dimensional double vector. +typedef Vector Vector3d; + +/// \ingroup vector_aliases +/// @brief Type alias for a four-dimensional double vector. +typedef Vector Vector4d; + + +} // namespace geom +} // namespace math + + + +#endif // SCMATH_VECTORS_H_H diff --git a/include/scmp/math.h b/include/scmp/math.h new file mode 100644 index 0000000..3c5861c --- /dev/null +++ b/include/scmp/math.h @@ -0,0 +1,77 @@ +/// math.h provides certain useful mathematical functions. + +#ifndef SCCCL_MATH_H +#define SCCCL_MATH_H + +#include +#include + +namespace scmath { + + +// MAX_RADIAN is a precomputed 2 * M_PI, and MIN_RADIAN is -2 * M_PI. +constexpr double MAX_RADIAN = 2 * M_PI; +constexpr double MIN_RADIAN = -2 * M_PI; +constexpr double POS_HALF_RADIAN = M_PI / 2; +constexpr double NEG_HALF_RADIAN = -(M_PI / 2); + + +/// Roll m die of n sides, returning a vector of the dice. +std::vector Die(int m, int n); +/// Roll m die of n sides, returning the total of the die. +int DieTotal(int m, int n); +/// Roll m die of n sides, and take the total of the top k die. +int BestDie(int k, int m, int n); + + +/// Convert radians to degrees. +/// @param rads the angle in radians +/// @return the angle in degrees. +float RadiansToDegreesF(float rads); + +/// Convert radians to degrees. +/// @param rads the angle in radians +/// @return the angle in degrees. +double RadiansToDegreesD(double rads); + +/// Convert degrees to radians. +/// @param degrees the angle in degrees +/// @return the angle in radians. +float DegreesToRadiansF(float degrees); + +/// Convert degrees to radians. +/// @param degrees the angle in degrees +/// @return the angle in radians. +double DegreesToRadiansD(double degrees); + +/// RotateRadians rotates theta0 by theta1 radians, wrapping the result to +/// MIN_RADIAN <= result <= MAX_RADIAN. +double RotateRadians(double theta0, double theta1); + +/// Get the default epsilon value. +/// @param epsilon The variable to store the epsilon value in. +void DefaultEpsilon(double &epsilon); + +/// Get the default epsilon value. +/// @param epsilon The variable to store the epsilon value in. +void DefaultEpsilon(float &epsilon); + + +/// Return whether the two values of type T are equal to within some tolerance. +/// @tparam T The type of value +/// @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. +/// @param epsilon The tolerance value. +/// @return Whether the two values are "close enough" to be considered equal. +template +static T +WithinTolerance(T a, T b, T epsilon) +{ + return std::abs(a - b) < epsilon; +} + + +} // namespace math + + +#endif //SCCCL_MATH_H diff --git a/include/scmp/motion2d.h b/include/scmp/motion2d.h new file mode 100644 index 0000000..fb2a184 --- /dev/null +++ b/include/scmp/motion2d.h @@ -0,0 +1,21 @@ +// +// Created by Kyle Isom on 2/21/20. +// + +#ifndef SCCCL_MOTION2D_H +#define SCCCL_MOTION2D_H + + +#include + +namespace scphys { +namespace basic { + + +scmath::geom::Vector2d Acceleration(double speed, double heading); + + +} // namespace basic +} // namespace phsyics + +#endif //SCCCL_MOTION2D_H diff --git a/Arena.h b/include/scsl/Arena.h similarity index 100% rename from Arena.h rename to include/scsl/Arena.h diff --git a/Buffer.h b/include/scsl/Buffer.h similarity index 100% rename from Buffer.h rename to include/scsl/Buffer.h diff --git a/Commander.h b/include/scsl/Commander.h similarity index 100% rename from Commander.h rename to include/scsl/Commander.h diff --git a/Dictionary.h b/include/scsl/Dictionary.h similarity index 100% rename from Dictionary.h rename to include/scsl/Dictionary.h diff --git a/Exceptions.h b/include/scsl/Exceptions.h similarity index 100% rename from Exceptions.h rename to include/scsl/Exceptions.h diff --git a/Flag.h b/include/scsl/Flag.h similarity index 100% rename from Flag.h rename to include/scsl/Flag.h diff --git a/StringUtil.h b/include/scsl/StringUtil.h similarity index 100% rename from StringUtil.h rename to include/scsl/StringUtil.h diff --git a/TLV.h b/include/scsl/TLV.h similarity index 100% rename from TLV.h rename to include/scsl/TLV.h diff --git a/scsl.h b/include/scsl/scsl.h similarity index 100% rename from scsl.h rename to include/scsl/scsl.h diff --git a/Test.h b/include/sctest/Assert.h similarity index 100% rename from Test.h rename to include/sctest/Assert.h diff --git a/include/sctest/Report.h b/include/sctest/Report.h new file mode 100755 index 0000000..a053490 --- /dev/null +++ b/include/sctest/Report.h @@ -0,0 +1,48 @@ +// +// Project: scccl +// File: include/test/Report.h +// Author: Kyle Isom +// Date: 2017-06-05 +// Namespace: test +// +// Report.h defines a Report structure that contains information about +// the results of unit tests. +// +// Copyright 2017 Kyle Isom +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 __SCTEST_REPORT_H +#define __SCTEST_REPORT_H + +#include + +namespace sctest { + +typedef struct _Report { + // Failing stores the number of failing tests; for tests added + // with AddTest, this is a test that returned false. For tests + // added with AddFailingTest, this is a test that returned true. + size_t Failing; + + // Total is the number of tests registered during the last run. + size_t Total; + + std::chrono::time_point Start; + std::chrono::time_point End; + std::chrono::duration Duration; + + _Report(); +} Report; + +} // end namespace test +#endif diff --git a/include/sctest/SimpleSuite.h b/include/sctest/SimpleSuite.h new file mode 100755 index 0000000..e37c96d --- /dev/null +++ b/include/sctest/SimpleSuite.h @@ -0,0 +1,87 @@ +// +// Project: scccl +// File: include/test/SimpleSuite.h +// Author: Kyle Isom +// Date: 2017-06-05 +// Namespace: test +// +// SimpleSuite.h defines the SimpleSuite class for unit testing. +// +// Copyright 2017 Kyle Isom +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 __SCTEST_SIMPLESUITE_H +#define __SCTEST_SIMPLESUITE_H + +// SimpleSuite.h +// This header file defines the interface for a simple suite of tests. + +#include +#include +#include + +#include + +namespace sctest { + +typedef struct { + std::string name; + std::function test; +} TestCase; + +class SimpleSuite { + public: + SimpleSuite(); + + // Silence suppresses output. + void Silence(void) { quiet = true; } + + // Setup defines a setup function; this should be a predicate. This function + // is called at the start of the Run method, before tests are run. + void Setup(std::function setupFn) { fnSetup = setupFn; } + + // Teardown defines a teardown function; this should be a predicate. This + // function is called at the end of the Run method, after all tests have run. + void Teardown(std::function teardownFn) { fnTeardown = teardownFn; } + + // AddTest is used to add a test that is expected to return true. + void AddTest(std::string, std::function); + + // AddFailingTest is used to add a test that is expected to return false. + void AddFailingTest(std::string, std::function); + + bool Run(void); + + // Reporting methods. + + // Reset clears the report statistics. + void Reset(void) { report.Failing = report.Total = 0; hasRun = false; }; + + // IsReportReady returns true if a report is ready. + bool IsReportReady(void) { return hasRun; } + + // Report returns a Report. + Report GetReport(void); + + private: + bool quiet; + std::function fnSetup, fnTeardown; + std::vector tests; + + // Report functions. + Report report; + bool hasRun; // Have the tests been run yet? +}; + +} // end namespace test +#endif diff --git a/include/sctest/checks.h b/include/sctest/checks.h new file mode 100755 index 0000000..b9293be --- /dev/null +++ b/include/sctest/checks.h @@ -0,0 +1,50 @@ +// +// Project: scccl +// File: include/test/checks.h +// Author: Kyle Isom +// Date: 2017-06-05 +// Namespace: test. +// +// checks.h defines a number of macros (which are global in scope) for +// use in test functions that return bools. +// +// Copyright 2017 Kyle Isom +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 __SCTEST_CHECKS_H +#define __SCTEST_CHECKS_H + +#include + + +namespace sctest { + + +// The following checks are designed as shortcuts that just return false on certain +// conditions. +#define SCTEST_CHECK(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_NE(x, y) if ((x) == (y)) { return false; } +#define SCTEST_CHECK_ZERO(x) if ((x) != 0) { return false; } +#define SCTEST_CHECK_GTZ(x) if ((x) > 0) { return false; } +#define SCTEST_CHECK_GEZ(x) if ((x) >= 0) { return false; } +#define SCTEST_CHECK_LEZ(x) if ((x) <= 0) { return false; } +#define SCTEST_CHECK_LTZ(x) if ((x) < 0) { return false; } +#define SCTEST_CHECK_FEQ(x, y) { float eps; scmath::DefaultEpsilon(eps); if (!scmath::WithinTolerance((x), (y), eps)) { return false; }} +#define SCTEST_CHECK_DEQ(x, y) { double eps; scmath::DefaultEpsilon(eps); if (!scmath::WithinTolerance((x), (y), eps)) { return false; }} + +} // namespace test + + +#endif diff --git a/include/sctest/debug.h b/include/sctest/debug.h new file mode 100755 index 0000000..9e46819 --- /dev/null +++ b/include/sctest/debug.h @@ -0,0 +1,57 @@ +// +// Project: scccl +// File: include/test/debug.h +// Author: Kyle Isom +// Date: 2017-06-05 +// Namespace: test +// +// debug.h defines assertions and other debugging functions. +// +// Copyright 2017 Kyle Isom +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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. +#if 0 +// Disabled for now. +#pragma once + +#include +#ifndef NDEBUG +#include +#endif + +namespace test { + +// GenerateCoreDumps should be set at the beginning of the program, before +// multithreading. It is *not* threadsafe. +static bool GenerateCoreDumps = false; + +static void +Assert(bool cond) { + #ifdef NDEBUG + std::cout << "Not a debug build, skipping assertion." << std::endl; + return; + #endif + + if (!cond) { + std::cerr << "Assertion failed in " << __func__ << "(" << __FILE__ << ":" << __LINE__ << ")" << std::endl; + if (GenerateCoreDumps) { + std::abort(); + } + else { + std::exit(1); + } + } +} + +} +#endif \ No newline at end of file diff --git a/phonebook.cc b/src/bin/phonebook.cc similarity index 98% rename from phonebook.cc rename to src/bin/phonebook.cc index 8434be8..53e4336 100644 --- a/phonebook.cc +++ b/src/bin/phonebook.cc @@ -24,10 +24,9 @@ #include using namespace std; -#include "Arena.h" -#include "Commander.h" -#include "Dictionary.h" -#include "Flag.h" +#include +#include +#include using namespace scsl; static const char *defaultPhonebook = "pb.dat"; diff --git a/src/scmp/coord2d.cc b/src/scmp/coord2d.cc new file mode 100755 index 0000000..f8021fa --- /dev/null +++ b/src/scmp/coord2d.cc @@ -0,0 +1,181 @@ +// +// Project: scccl +// File: src/math/geom2d.cpp +// Author: Kyle Isom +// Date: 2017-06-05 +// Namespace: math::geom +// +// geom2d.cpp contains the implementation of 2D geometry in the math::geom +// namespace. +// +// Copyright 2017 Kyle Isom +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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. +#include +#include +#include + +#include +#include + + +// coord2d.cpp contains 2D geometric functions and data structures, such as +// cartesian and polar coordinates and rotations. + +// TODO: deprecate Point2D in favour of Vector + +namespace scmath { +namespace geom { + + +// +// Point2D + +Point2D::Point2D(const Polar2D &pol) + : x(std::rint(std::cos(pol.theta) * pol.r)), + y(std::rint(std::sin(pol.theta) * pol.r)) {} + + +std::ostream& +operator<<(std::ostream& outs, const Point2D& pt) +{ + outs << "(" << std::to_string(pt.x) << ", " << std::to_string(pt.y) << ")"; + return outs; +} + + +std::string +Point2D::ToString() +{ + return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; +} + + +void +Point2D::ToPolar(Polar2D& pol) +{ + pol.r = std::sqrt((x * x) + (y * y)); + pol.theta = std::atan2(y, x); +} + + +void +Point2D::Rotate(Point2D& pt, double theta) +{ + Polar2D pol(*this); + pol.Rotate(pol, theta); + pol.ToPoint(pt); +} + + +bool +Point2D::operator==(const Point2D& rhs) const +{ + return (x == rhs.x) && (y == rhs.y); +} + + +void +Point2D::Translate(const Point2D& origin, Point2D &translated) +{ + translated.x = origin.x + x; + translated.y = origin.y + y; +} + + +std::vector +Point2D::Rotate(std::vector vertices, double theta) +{ + std::vector rotated; + + for (auto v : vertices) { + Point2D p; + v.RotateAround(*this, p, theta); + rotated.push_back(p) ; + } + + return rotated; +} + + +int +Point2D::Distance(const Point2D& other) +{ + auto dx = other.x - x; + auto dy = other.y - y; + return std::sqrt(dx * dx + dy + dy); +} + + +// Polar2D + +Polar2D::Polar2D(const Point2D &pt) + : r(std::sqrt((pt.x * pt.x) + (pt.y * pt.y))), + theta(std::atan2(pt.y, pt.x)) {} + + +void +Polar2D::ToPoint(Point2D& pt) +{ + pt.y = std::rint(std::sin(theta) * r); + pt.x = std::rint(std::cos(theta) * r); +} + + +std::string +Polar2D::ToString() +{ + return "(" + std::to_string(r) + ", " + std::to_string(theta) + ")"; +} + + +void +Polar2D::Rotate(Polar2D& rot, double delta) +{ + rot.r = r; + rot.theta = RotateRadians(theta, delta); +} + + +bool +Polar2D::operator==(const Polar2D& rhs) const +{ + static double eps = 0.0; + if (eps == 0.0) { + scmath::DefaultEpsilon(eps); + } + return scmath::WithinTolerance(r, rhs.r, eps) && + scmath::WithinTolerance(theta, rhs.theta, eps); +} + + +void +Polar2D::RotateAround(const Point2D &origin, Point2D &point, double delta) +{ + Polar2D rot; + this->Rotate(rot, delta); + rot.ToPoint(point); + point.Translate(origin, point); +} + + +std::ostream& +operator<<(std::ostream& outs, const Polar2D& pol) +{ + outs << "(" << pol.r << ", " << pol.theta << ")"; + return outs; +} + + +} // end namespace geom +} // end namespace math diff --git a/src/scmp/math.cc b/src/scmp/math.cc new file mode 100644 index 0000000..0dd0980 --- /dev/null +++ b/src/scmp/math.cc @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include + +#include + + +namespace scmath { + + +std::vector +Die(int m, int n) +{ + std::uniform_int_distribution<> die(1, n); + + std::random_device rd; + std::vector dice; + int i = 0; + + for (i = 0; i < m; i++) { + dice.push_back(die(rd)); + } + + return dice; +} + + +int +BestDie(int k, int m, int n) +{ + auto dice = Die(m, n); + + if (k < m) { + std::sort(dice.begin(), dice.end(), std::greater()); + dice.resize(static_cast(k)); + } + + return std::accumulate(dice.begin(), dice.end(), 0); +} + + +int +DieTotal(int m, int n) +{ + std::uniform_int_distribution<> die(1, n); + + std::random_device rd; + int i = 0, total = 0; + + for (i = 0; i < m; i++) { + total += die(rd); + } + + return total; +} + + +float +RadiansToDegreesF(float rads) +{ + return rads * (180.0 / M_PI); +} + + +double +RadiansToDegreesD(double rads) +{ + return rads * (180.0 / M_PI); +} + + +float +DegreesToRadiansF(float degrees) +{ + return degrees * M_PI / 180.0; +} + + +double +DegreesToRadiansD(double degrees) +{ + return degrees * M_PI / 180.0; +} + + +double +RotateRadians(double theta0, double theta1) +{ + auto dtheta = theta0 + theta1; + + if (dtheta > M_PI) { + dtheta -= MAX_RADIAN; + } else if (dtheta < -M_PI) { + dtheta += MAX_RADIAN; + } + + if ((dtheta < -M_PI) || (dtheta > M_PI)) { + return RotateRadians(dtheta, 0); + } + + return dtheta; +} + + + + +const double Epsilon_double = 0.0001; +const float Epsilon_float = 0.0001; + + +void +DefaultEpsilon(double &epsilon) +{ + epsilon = Epsilon_double; +} + + +void +DefaultEpsilon(float &epsilon) +{ + epsilon = Epsilon_float; +} + + +} // namespace math + diff --git a/src/scmp/motion2d.cc b/src/scmp/motion2d.cc new file mode 100644 index 0000000..8582ae3 --- /dev/null +++ b/src/scmp/motion2d.cc @@ -0,0 +1,19 @@ +#include +#include + +namespace scphys { +namespace basic { + + +scmath::geom::Vector2d +Acceleration(double speed, double heading) +{ + auto dx = std::cos(heading) * speed; + auto dy = std::sin(heading) * speed; + + return scmath::geom::Vector2d({dx, dy}); +} + + +} // namespace basic +} // namespace phys \ No newline at end of file diff --git a/src/scmp/orientation.cc b/src/scmp/orientation.cc new file mode 100644 index 0000000..4232616 --- /dev/null +++ b/src/scmp/orientation.cc @@ -0,0 +1,40 @@ +#include +#include + + +namespace scmath { +namespace geom { + + +float +Heading2f(Vector2f vec) +{ + return vec.angle(Basis2f[Basis_x]); +} + + +float +Heading3f(Vector3f vec) +{ + Vector2f vec2f {vec[0], vec[1]}; + return Heading2f(vec2f); +} + + +double +Heading2d(Vector2d vec) +{ + return vec.angle(Basis2d[Basis_x]); +} + + +double +Heading3d(Vector3d vec) +{ + Vector2d vec2d {vec[0], vec[1]}; + return Heading2d(vec2d); +} + + +} // namespace geom +} // namespace math diff --git a/src/scmp/quaternion.cc b/src/scmp/quaternion.cc new file mode 100644 index 0000000..6cf5a61 --- /dev/null +++ b/src/scmp/quaternion.cc @@ -0,0 +1,91 @@ +#include + +#include + + +namespace scmath { +namespace geom { + + +Quaternionf +quaternionf(Vector3f axis, float angle) +{ + return Quaternionf(axis.unitVector() * std::sin(angle / 2.0), + std::cos(angle / 2.0)); +} + + +Quaterniond +quaterniond(Vector3d axis, double angle) +{ + return Quaterniond(axis.unitVector() * std::sin(angle / 2.0), + std::cos(angle / 2.0)); +} + + +Quaternionf +quaternionf_from_euler(Vector3f euler) +{ + float x, y, z, w; + euler = euler / 2.0; + + float cos_yaw = std::cos(euler[0]); + float cos_pitch = std::cos(euler[1]); + float cos_roll = std::cos(euler[2]); + float sin_yaw = std::sin(euler[0]); + float sin_pitch = std::sin(euler[1]); + float sin_roll = std::sin(euler[2]); + + x = (sin_yaw * cos_pitch * cos_roll) + (cos_yaw * sin_pitch * sin_roll); + y = (sin_yaw * cos_pitch * sin_roll) - (cos_yaw * sin_pitch * cos_roll); + z = (cos_yaw * cos_pitch * sin_roll) + (sin_yaw * sin_pitch * cos_roll); + w = (cos_yaw * cos_pitch * cos_roll) - (sin_yaw * sin_pitch * sin_roll); + + return Quaternionf(Vector4f{w, x, y, z}); +} + + +Quaterniond +quaterniond_from_euler(Vector3d euler) +{ + double x, y, z, w; + euler = euler / 2.0; + + double cos_yaw = std::cos(euler[0]); + double cos_pitch = std::cos(euler[1]); + double cos_roll = std::cos(euler[2]); + double sin_yaw = std::sin(euler[0]); + double sin_pitch = std::sin(euler[1]); + double sin_roll = std::sin(euler[2]); + + x = (sin_yaw * cos_pitch * cos_roll) + (cos_yaw * sin_pitch * sin_roll); + y = (sin_yaw * cos_pitch * sin_roll) - (cos_yaw * sin_pitch * cos_roll); + z = (cos_yaw * cos_pitch * sin_roll) + (sin_yaw * sin_pitch * cos_roll); + w = (cos_yaw * cos_pitch * cos_roll) - (sin_yaw * sin_pitch * sin_roll); + + return Quaterniond(Vector4d{w, x, y, z}); +} + + +void +Quaternion_SelfTest() +{ +#ifndef NDEBUG + Vector3f v {1.0, 0.0, 0.0}; + Vector3f yAxis {0.0, 1.0, 0.0}; + float angle = M_PI / 2; + + Quaternionf p = quaternionf(yAxis, angle); + Quaternionf q; + Vector3f vr {0.0, 0.0, 1.0}; + + assert(p.isUnitQuaternion()); + std::cerr << p.rotate(v) << std::endl; + assert(p.rotate(v) == vr); + assert(p * q == p); +#endif +} + + +} // namespace geom +} // namespace math diff --git a/Arena.cc b/src/sl/Arena.cc similarity index 99% rename from Arena.cc rename to src/sl/Arena.cc index e9f1c56..3ade9cb 100644 --- a/Arena.cc +++ b/src/sl/Arena.cc @@ -36,7 +36,7 @@ #include -#include "Arena.h" +#include namespace scsl { diff --git a/Buffer.cc b/src/sl/Buffer.cc similarity index 99% rename from Buffer.cc rename to src/sl/Buffer.cc index 908acfa..2c3462d 100644 --- a/Buffer.cc +++ b/src/sl/Buffer.cc @@ -26,7 +26,8 @@ #include #include -#include "Buffer.h" +#include + namespace scsl { diff --git a/Commander.cc b/src/sl/Commander.cc similarity index 97% rename from Commander.cc rename to src/sl/Commander.cc index cea9020..812f122 100644 --- a/Commander.cc +++ b/src/sl/Commander.cc @@ -22,7 +22,7 @@ #include -#include "Commander.h" +#include namespace scsl { diff --git a/Dictionary.cc b/src/sl/Dictionary.cc similarity index 98% rename from Dictionary.cc rename to src/sl/Dictionary.cc index a55410b..0cb7c24 100644 --- a/Dictionary.cc +++ b/src/sl/Dictionary.cc @@ -24,7 +24,7 @@ #include #include -#include "Dictionary.h" +#include #if defined(SCSL_DESKTOP_BUILD) #include diff --git a/Exceptions.cc b/src/sl/Exceptions.cc similarity index 97% rename from Exceptions.cc rename to src/sl/Exceptions.cc index 334d5cc..637fd6b 100644 --- a/Exceptions.cc +++ b/src/sl/Exceptions.cc @@ -20,7 +20,7 @@ /// PERFORMANCE OF THIS SOFTWARE. /// -#include "Exceptions.h" +#include namespace scsl { diff --git a/Flag.cc b/src/sl/Flag.cc similarity index 99% rename from Flag.cc rename to src/sl/Flag.cc index e4cc56f..dab14f9 100644 --- a/Flag.cc +++ b/src/sl/Flag.cc @@ -25,8 +25,8 @@ #include #include -#include "Flag.h" -#include "StringUtil.h" +#include +#include namespace scsl { diff --git a/src/sl/Roll.cc b/src/sl/Roll.cc new file mode 100644 index 0000000..4755c22 --- /dev/null +++ b/src/sl/Roll.cc @@ -0,0 +1,76 @@ +#include +#include +#include + +#include +using namespace std; +using namespace scmath; + + +static void +rollDie(char *s) +{ + int m = 0, n = 0; + int i = 0; + bool readSides = false; + + while (s[i] != '\0') { + if (s[i] != 'd' && !isdigit(s[i])) { + cerr << "Invalid die specification!" << endl; + return; + } + + if (readSides) { + if (s[i] == 'd') { + cerr << "Invalid die specification!" << endl; + return; + } + n *= 10; + n += (s[i] - 0x30); + } else { + if (s[i] == 'd') { + readSides = true; + } else { + m *= 10; + m += (s[i] - 0x30); + } + } + + i++; + } + + if (m == 0) { + m = 1; + } + + cout << s << ": " << DieTotal(m, n) << endl; +} + + +static void +rollPlayer() +{ + vector statNames = {"STR", "CON", "DEX", "INT", "PER"}; + vector statRolls; + + for (size_t i = 0; i < statNames.size(); i++) { + statRolls.push_back(BestDie(3, 4, 6)); + } + + for (size_t i = 0; i < statNames.size(); i++) { + cout << statNames[i] << ": " << statRolls[i] << endl; + } +} + + +int +main(int argc, char *argv[]) +{ + for (int i = 1; i < argc; i++) { + if (string(argv[i]) == "player") { + rollPlayer(); + } else { + rollDie(argv[i]); + } + } +} diff --git a/StringUtil.cc b/src/sl/StringUtil.cc similarity index 99% rename from StringUtil.cc rename to src/sl/StringUtil.cc index c702fe8..a0c56c2 100644 --- a/StringUtil.cc +++ b/src/sl/StringUtil.cc @@ -24,7 +24,7 @@ #include #include -#include "StringUtil.h" +#include namespace scsl { diff --git a/TLV.cc b/src/sl/TLV.cc similarity index 99% rename from TLV.cc rename to src/sl/TLV.cc index 1fbcfb5..eb500a4 100644 --- a/TLV.cc +++ b/src/sl/TLV.cc @@ -23,7 +23,7 @@ #include #include -#include "TLV.h" +#include using namespace scsl; diff --git a/Test.cc b/src/test/Assert.cc similarity index 96% rename from Test.cc rename to src/test/Assert.cc index 2aad211..f7980c6 100644 --- a/Test.cc +++ b/src/test/Assert.cc @@ -20,8 +20,8 @@ /// PERFORMANCE OF THIS SOFTWARE. /// -#include "Exceptions.h" -#include "Test.h" +#include +#include #include #include diff --git a/src/test/Report.cc b/src/test/Report.cc new file mode 100644 index 0000000..5f7843f --- /dev/null +++ b/src/test/Report.cc @@ -0,0 +1,37 @@ +/// +/// \file src/test/Report.cpp +/// \author Kyle Isom +/// \date 2017-06-07 +/// +/// \brief Defines a Report structure that contains information about +/// the results of unit tests. +/// +/// Copyright 2017 K. Isom +/// +/// 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 + +#include + + +namespace sctest { + + +_Report::_Report() + : Failing (0), Total(0), Start(std::chrono::steady_clock::now()), + End(std::chrono::steady_clock::now()), Duration(0) {} + + +} // end namespace test \ No newline at end of file diff --git a/src/test/SimpleSuite.cc b/src/test/SimpleSuite.cc new file mode 100755 index 0000000..c057da8 --- /dev/null +++ b/src/test/SimpleSuite.cc @@ -0,0 +1,108 @@ +/// +/// \file SimpleSuite.cc +/// \author K. Isom +/// \date 2017-06-05 +/// \brief Defines a simple unit testing framework. +/// +/// Copyright 2017 K. Isom +/// +/// 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 +#include + +#include + + +namespace sctest { + +#define unless(cond) if (!(cond)) + + +static bool +stub() +{ return true; } + +SimpleSuite::SimpleSuite() + : quiet(false), fnSetup(stub), fnTeardown(stub), tests(), + report(), hasRun(false) +{ + +} + + +void +SimpleSuite::AddTest(std::string name, std::function test) +{ + TestCase test_case = {name, test}; + tests.push_back(test_case); +} + + +void +SimpleSuite::AddFailingTest(std::string name, std::function test) +{ + // auto ntest = [&test]() { return !test(); }; + TestCase test_case = {name, [&test]() { return !test(); }}; + tests.push_back(test_case); +} + + +bool +SimpleSuite::Run() +{ + report.Start = std::chrono::steady_clock::now(); + unless(quiet) { std::cout << "Setting up the tests.\n"; } + unless(fnSetup()) { return false; } + + // Reset the failed test counts. + report.Failing = 0; + + bool result = true; + hasRun = true; + report.Total = tests.size(); + for (size_t i = 0; i < report.Total && result; i++) { + TestCase tc = tests.at(i); + unless(quiet) { + std::cout << "[" << i + 1 << "/" << report.Total << "] Running test " << tc.name << ": "; + } + + result = tc.test(); + if (quiet) { continue; } + + if (result) { + std::cout << "[PASS]"; + } else { + std::cout << "[FAIL]"; + report.Failing++; + } + std::cout << "\n"; + } + + unless(quiet) { std::cout << "Tearing down the tests.\n"; } + unless(fnTeardown()) { return false; } + report.End = std::chrono::steady_clock::now(); + return result; +} + + +Report +SimpleSuite::GetReport() +{ + return report; +} + + +} // end namespace sctest \ No newline at end of file diff --git a/bufferTest.cc b/test/buffer.cc similarity index 98% rename from bufferTest.cc rename to test/buffer.cc index aff9a99..5383ec6 100644 --- a/bufferTest.cc +++ b/test/buffer.cc @@ -1,7 +1,7 @@ #include #include -#include "Buffer.h" +#include using namespace scsl; diff --git a/dictionaryTest.cc b/test/dictionary.cc similarity index 96% rename from dictionaryTest.cc rename to test/dictionary.cc index b888a76..4ba4c6b 100644 --- a/dictionaryTest.cc +++ b/test/dictionary.cc @@ -1,9 +1,9 @@ #include -#include "Arena.h" -#include "Dictionary.h" -#include "Test.h" -#include "testFixtures.h" +#include +#include +#include +#include "test_fixtures.h" using namespace scsl; diff --git a/flagTest.cc b/test/flag.cc similarity index 97% rename from flagTest.cc rename to test/flag.cc index a465893..446e568 100644 --- a/flagTest.cc +++ b/test/flag.cc @@ -4,8 +4,8 @@ #include -#include "Flag.h" -#include "Test.h" +#include +#include using namespace scsl; diff --git a/test/simple_suite_example.cc b/test/simple_suite_example.cc new file mode 100755 index 0000000..1117d36 --- /dev/null +++ b/test/simple_suite_example.cc @@ -0,0 +1,72 @@ +// +// Project: scccl +// File: test/math/simple_suite_example.cpp +// Author: Kyle Isom +// Date: 2017-06-05 +// +// simple_suite_example demonstrates the usage of the SimpleSuite test class +// and serves to unit test the unit tester (qui custodiet ipsos custodes)? +// +// Copyright 2017 Kyle Isom +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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. + +#include + +#include + +static bool +prepareTests() +{ + std::cout << "time passes...\n"; + std::cout << "tests are ready.\n"; + return true; +} + +static bool +destroyTests() +{ + std::cout << "time passes...\n" ; + std::cout << "tests have been destroyed.\n"; + return true; +} + +static bool addOne() { return 1 + 1 == 2; } +static bool four() { return 2 + 2 == 4; } +static bool nope() { return 2 + 2 == 5; } + + +int +main() +{ + sctest::SimpleSuite TestSuite; + TestSuite.Setup(prepareTests); + TestSuite.Teardown(destroyTests); + TestSuite.AddTest("1 + 1", addOne); + TestSuite.AddTest("fourness", four); + TestSuite.AddFailingTest("self-evident truth", nope); + + bool result = TestSuite.Run(); + if (TestSuite.IsReportReady()) { + auto report = TestSuite.GetReport(); + std::cout << report.Failing << " / " << report.Total; + std::cout << " tests failed.\n"; + } + + if (result) { + return 0; + } + else { + return 1; + } +} diff --git a/stringutil_test.cc b/test/stringutil.cc similarity index 98% rename from stringutil_test.cc rename to test/stringutil.cc index 3740817..47b0237 100644 --- a/stringutil_test.cc +++ b/test/stringutil.cc @@ -24,8 +24,8 @@ #include #include -#include "StringUtil.h" -#include "Test.h" +#include +#include using namespace scsl; diff --git a/testFixtures.h b/test/test_fixtures.h similarity index 97% rename from testFixtures.h rename to test/test_fixtures.h index 79c87a5..b495650 100644 --- a/testFixtures.h +++ b/test/test_fixtures.h @@ -3,7 +3,7 @@ #include -#include "TLV.h" +#include #define ARENA_SIZE 128 diff --git a/tlvTest.cc b/test/tlv.cc similarity index 96% rename from tlvTest.cc rename to test/tlv.cc index f274ca1..e283683 100644 --- a/tlvTest.cc +++ b/test/tlv.cc @@ -3,11 +3,11 @@ #include #include -#include "Arena.h" -#include "Test.h" -#include "TLV.h" +#include +#include +#include -#include "testFixtures.h" +#include "test_fixtures.h" using namespace scsl;