From 0fdde28faace3e2405b4943023a9106801ba95fd Mon Sep 17 00:00:00 2001 From: Mark Polyakov Date: Tue, 6 Apr 2021 13:36:29 -0700 Subject: [PATCH] "MATHS" --Sean Murray --- Makefile | 13 +- compile_flags.txt | 2 + src/attitude-estimators.cpp | 114 +- src/attitude-utils.cpp | 34 + src/attitude-utils.hpp | 14 +- src/camera.cpp | 54 +- src/camera.hpp | 32 +- src/io.cpp | 23 +- src/io.hpp | 2 +- src/star-id.cpp | 29 +- src/star-utils.hpp | 2 + test/geometry.cpp | 31 + test/kvector.cpp | 32 + test/main.cpp | 2 + vendor/catch.hpp | 17881 ++++++++++++++++++++++++++++++++++ 15 files changed, 18152 insertions(+), 113 deletions(-) create mode 100644 test/geometry.cpp create mode 100644 test/kvector.cpp create mode 100644 test/main.cpp create mode 100644 vendor/catch.hpp diff --git a/Makefile b/Makefile index fb45b4ee..ea81e63f 100644 --- a/Makefile +++ b/Makefile @@ -22,14 +22,17 @@ # https://stackoverflow.com/q/2394609) SRCS := $(wildcard src/*.cpp) +TESTS := $(wildcard test/*.cpp) OBJS := $(patsubst %.cpp,%.o,$(SRCS)) +TEST_OBJS := $(patsubst %.cpp,%.o,$(TESTS) $(filter-out %/main.o, $(OBJS))) DEPS := $(patsubst %.cpp,%.d,$(SRCS)) BIN := lost +TEST_BIN := ./lost-test BSC := bright-star-catalog.tsv LIBS := -lcairo -CXXFLAGS := $(CXXFLAGS) -Ivendor -Wall --std=c++11 +CXXFLAGS := $(CXXFLAGS) -Ivendor -Isrc -Wall --std=c++11 all: $(BIN) $(BSC) @@ -44,8 +47,14 @@ $(BIN): $(OBJS) -include $(DEPS) +test: $(TEST_BIN) + $(TEST_BIN) + +$(TEST_BIN): $(TEST_OBJS) + $(CXX) $(LDFLAGS) -o $(TEST_BIN) $(TEST_OBJS) $(LIBS) + clean: rm -f $(OBJS) $(DEPS) rm -i $(BSC) -.PHONY: all clean +.PHONY: all clean test diff --git a/compile_flags.txt b/compile_flags.txt index c24e3b5e..d0add4db 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1 +1,3 @@ -std=c++11 +-Ivendor +-Isrc diff --git a/src/attitude-estimators.cpp b/src/attitude-estimators.cpp index 16909c0f..9ec6c397 100644 --- a/src/attitude-estimators.cpp +++ b/src/attitude-estimators.cpp @@ -3,64 +3,66 @@ #include namespace lost { - Quaternion DavenportQAlgorithm::Go(const Camera &cameraBoy, const Stars &starBoy, const Catalog &catalogBoy, const StarIdentifiers &StarIdentifiersBoy) { - //create a vector that'll hold {bi} (Stars in our frame) - //create a vector that'll hold {ri} (Stars in catalog frame) - Eigen::Matrix3f B; - B.setZero(); - for (const StarIdentifier &s: StarIdentifiersBoy) { - Star bStar = starBoy[s.starIndex]; - float ra; - float dej; - cameraBoy.CoordinateAngles({bStar.x, bStar.y}, &ra, &dej); - Eigen::Vector3f bi; - bi << cos(ra)*cos(dej), - sin(ra)*cos(dej), - sin(dej); - CatalogStar rStar = catalogBoy[s.catalogIndex]; - Eigen::Vector3f ri; - ri << cos(rStar.raj2000)*cos(rStar.dej2000), - sin(rStar.raj2000)*cos(rStar.dej2000), - sin(rStar.dej2000); +Quaternion DavenportQAlgorithm::Go(const Camera &camera, + const Stars &stars, + const Catalog &catalog, + const StarIdentifiers &starIdentifiers) { - //Weight = 1 (can be changed later, in which case we want to make a vector to hold all weights {ai}) - //Calculate matrix B = sum({ai}{bi}{ri}T) - B += ri * bi.transpose() * s.weight; - } - // S = B + Transpose(B) - Eigen::Matrix3f S = B + B.transpose(); - //sigma = B[0][0] + B[1][1] + B[2][2] - float sigma = B.trace(); - //Z = [[B[1][2] - B[2][1]], [B[2][0] - B[0][2]], [B[0][1] - B[1][0]]] - Eigen::Vector3f Z; - Z << B(1,2) - B(2,1), - B(2,0) - B(0,2), - B(0,1) - B(1,0); - //K = [[[sigma], [Z[0]], [Z[1]], [Z[2]]], [[Z[0]], [S[0][0] - sigma], [S[0][1]], [S[0][2]]], [[Z[1]], [S[1][0]], [S[1][1] - sigma], [S[1][2]]], [[Z[2]], [S[2][0]], [S[2][1]], [S[2][2] - sigma]]] - Eigen::Matrix4f K; - K << sigma, Z(0), Z(1), Z(2), - Z(0), S(0,0) - sigma, S(0,1), S(0,2), - Z(1), S(1,0), S(1,1) - sigma, S(1,2), - Z(2), S(2,0), S(2,1), S(2,2) - sigma; - //Find eigenvalues of K, store the largest one as lambda - //find the maximum index - Eigen::EigenSolver solver(K); - Eigen::Vector4cf values = solver.eigenvalues(); - Eigen::Matrix4cf vectors = solver.eigenvectors(); - int maxIndex = 0; - std::complex maxIndexValue = values(0); - for (int i = 1; i < values.size(); i++) { - if (values(i).real() > maxIndexValue.real()) { - maxIndex = i; - } + //create a vector that'll hold {bi} (Stars in our frame) + //create a vector that'll hold {ri} (Stars in catalog frame) + Eigen::Matrix3f B; + B.setZero(); + for (const StarIdentifier &s: starIdentifiers) { + Star bStar = stars[s.starIndex]; + Vec3 bStarSpatial = camera.CameraToSpatial({bStar.x, bStar.y}); + Eigen::Vector3f bi; + bi << bStarSpatial.y, bStarSpatial.x, bStarSpatial.z; + + CatalogStar rStar = catalog[s.catalogIndex]; + Eigen::Vector3f ri; + ri << cos(rStar.raj2000)*cos(rStar.dej2000), + sin(rStar.raj2000)*cos(rStar.dej2000), + sin(rStar.dej2000); + + //Weight = 1 (can be changed later, in which case we want to make a vector to hold all weights {ai}) + //Calculate matrix B = sum({ai}{bi}{ri}T) + B += ri * bi.transpose() * s.weight; + } + // S = B + Transpose(B) + Eigen::Matrix3f S = B + B.transpose(); + //sigma = B[0][0] + B[1][1] + B[2][2] + float sigma = B.trace(); + //Z = [[B[1][2] - B[2][1]], [B[2][0] - B[0][2]], [B[0][1] - B[1][0]]] + Eigen::Vector3f Z; + Z << B(1,2) - B(2,1), + B(2,0) - B(0,2), + B(0,1) - B(1,0); + //K = [[[sigma], [Z[0]], [Z[1]], [Z[2]]], [[Z[0]], [S[0][0] - sigma], [S[0][1]], [S[0][2]]], [[Z[1]], [S[1][0]], [S[1][1] - sigma], [S[1][2]]], [[Z[2]], [S[2][0]], [S[2][1]], [S[2][2] - sigma]]] + Eigen::Matrix4f K; + K << sigma, Z(0), Z(1), Z(2), + Z(0), S(0,0) - sigma, S(0,1), S(0,2), + Z(1), S(1,0), S(1,1) - sigma, S(1,2), + Z(2), S(2,0), S(2,1), S(2,2) - sigma; + //Find eigenvalues of K, store the largest one as lambda + //find the maximum index + Eigen::EigenSolver solver(K); + Eigen::Vector4cf values = solver.eigenvalues(); + Eigen::Matrix4cf vectors = solver.eigenvectors(); + int maxIndex = 0; + std::complex maxIndexValue = values(0); + for (int i = 1; i < values.size(); i++) { + if (values(i).real() > maxIndexValue.real()) { + maxIndex = i; } - //The return quaternion components = eigenvector assocaited with lambda - Eigen::Vector4cf maxAmount = vectors.col(maxIndex); - // IMPORTANT: The matrix K is symmetric -- clearly first row and first column are equal. - // Furthermore, S is symmetric because s_i,j = b_i,j + b_j,i and s_j,i=b_j,i + b_i,j, so the - // bottom right 3x3 block of K is symmetric too. Thus all its eigenvalues and eigenvectors - // are real. - return Quaternion(maxAmount[0].real(), maxAmount[1].real(), maxAmount[2].real(), maxAmount[3].real()); } + //The return quaternion components = eigenvector assocaited with lambda + Eigen::Vector4cf maxAmount = vectors.col(maxIndex); + // IMPORTANT: The matrix K is symmetric -- clearly first row and first column are equal. + // Furthermore, S is symmetric because s_i,j = b_i,j + b_j,i and s_j,i=b_j,i + b_i,j, so the + // bottom right 3x3 block of K is symmetric too. Thus all its eigenvalues and eigenvectors + // are real. + return Quaternion(maxAmount[0].real(), maxAmount[1].real(), maxAmount[2].real(), maxAmount[3].real()); +} + } diff --git a/src/attitude-utils.cpp b/src/attitude-utils.cpp index ee7fadc6..851e70ff 100644 --- a/src/attitude-utils.cpp +++ b/src/attitude-utils.cpp @@ -2,6 +2,7 @@ #include #include +#include namespace lost { @@ -64,6 +65,14 @@ Quaternion SphericalToQuaternion(float ra, float dec, float roll) { return (a*b*c).Conjugate(); } +Vec3 SphericalToSpatial(float ra, float de) { + return { + cos(ra)*cos(de), + sin(ra)*cos(de), + sin(de), + }; +} + float RadToDeg(float rad) { return rad*180.0/M_PI; } @@ -85,4 +94,29 @@ float GreatCircleDistance(float ra1, float de1, float ra2, float de2) { + cos(de1)*cos(de2)*pow(sin(abs(ra1-ra2)/2.0), 2.0))); } +float Vec3::Magnitude() const { + return sqrt(x*x+y*y+z*z); +} + +Vec3 Vec3::Normalize() const { + float mag = Magnitude(); + return { + x/mag, y/mag, z/mag, + }; +} + +float Vec3::operator*(const Vec3 &other) const { + return x*other.x + y*other.y + z*other.z; +} + +float Angle(const Vec3 &vec1, const Vec3 &vec2) { + return AngleUnit(vec1.Normalize(), vec2.Normalize()); +} + +float AngleUnit(const Vec3 &vec1, const Vec3 &vec2) { + // TODO: we shouldn't need this nonsense, right? how come acos sometimes gives nan? + float dot = vec1*vec2; + return dot >= 1 ? 0 : dot <= -1 ? -M_PI : acos(dot); +} + } diff --git a/src/attitude-utils.hpp b/src/attitude-utils.hpp index 7c2bf2ab..0177788b 100644 --- a/src/attitude-utils.hpp +++ b/src/attitude-utils.hpp @@ -14,10 +14,16 @@ struct Vec2 { float y; }; -struct Vec3 { +class Vec3 { +public: float x; float y; float z; + + float Magnitude() const; + Vec3 Normalize() const; + + float operator*(const Vec3 &) const; }; class Quaternion { @@ -46,6 +52,12 @@ class Quaternion { // will appear to rotate clockwise). This is an "improper" z-y'-x' Euler rotation. Quaternion SphericalToQuaternion(float ra, float dec, float roll); +Vec3 SphericalToSpatial(float ra, float de); +// angle between two vectors, using dot product and magnitude division +float Angle(const Vec3 &, const Vec3 &); +// angle between two vectors, /assuming/ that they are already unit length +float AngleUnit(const Vec3 &, const Vec3 &); + float RadToDeg(float); float DegToRad(float); float RadToArcSec(float); diff --git a/src/camera.cpp b/src/camera.cpp index 44f99de1..a3b8b770 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -6,36 +6,58 @@ namespace lost { -Vec2 Camera::ConvertCoordinates(const Vec3 &vector) const { +Vec2 Camera::SpatialToCamera(const Vec3 &vector) const { // can't handle things behind the camera. assert(vector.x > 0); - assert(xFov > 0 && xFov < M_PI); // TODO: is there any sort of accuracy problem when vector.y and vector.z are small? + float yTangent = vector.y/vector.x; float zTangent = vector.z/vector.x; - // these pixels are measured using 0,0 as the /center/ - // TODO: analyze off-by-one errors (is fov for the center of the pixels, in which case we should - // use xResolution-1??) - float yPixel = yTangent/tan(xFov/2)*(xResolution-1)/2; - float zPixel = zTangent/tan(xFov/2)*(xResolution-1)/2; + float yPixel = yTangent*xFocalLength; + float zPixel = zTangent*yFocalLength; - // now convert to using 0,0 as the top left, as usual. TODO: analyze off-by-one errors here too. - // Right now, if we have 5x5, the center becomes (2,2), and if we have 4x4, the center becomes - // (1.5, 1.5). I think this is right? - return { -yPixel + (xResolution-1)/2.0f, -zPixel + (yResolution-1)/2.0f }; + return { -yPixel + xCenter, -zPixel + yCenter }; } -void Camera::CoordinateAngles(const Vec2 &vector, float *ra, float *de) const { - // TODO: off-by-one with xResolution - 1? - // TODO: minimize floating point error? - *ra = atan((xResolution/2.0-vector.x)/(xResolution/2.0/tan(xFov/2.0))); - *de = atan((yResolution/2.0-vector.y)/(xResolution/2.0/tan(xFov/2.0))); +// we'll just place the points at 1 unit away from the pinhole (x=1) +Vec3 Camera::CameraToSpatial(const Vec2 &vector) const { + assert(InSensor(vector)); + + // isn't it interesting: To convert from center-based to left-corner-based coordinates is the + // same formula; f(x)=f^{-1}(x) ! + float xPixel = -vector.x + xCenter; + float yPixel = -vector.y + yCenter; + + return { + 1, + xPixel / xFocalLength, + yPixel / yFocalLength, + }; } +// TODO: reimplement or determine we don't need it +// void Camera::CoordinateAngles(const Vec2 &vector, float *ra, float *de) const { +// // TODO: off-by-one with xResolution - 1? +// // TODO: minimize floating point error? + +// // *ra = atan((xResolution/2.0-vector.x)/(xResolution/2.0/tan(xFov/2.0))); +// // *de = atan((yResolution/2.0-vector.y)/(xResolution/2.0/tan(xFov/2.0))); +// } + bool Camera::InSensor(const Vec2 &vector) const { + // if vector.x == xResolution, then it is at the leftmost point of the pixel that's "hanging + // off" the edge of the image, so vector is still in the image. return vector.x >= 0 && vector.x <= xResolution && vector.y >= 0 && vector.y <= yResolution; } +int Camera::GetXResolution() const { + return xResolution; +} + +int Camera::GetYResolution() const { + return yResolution; +} + } diff --git a/src/camera.hpp b/src/camera.hpp index e2a2bc09..db4f9fe4 100644 --- a/src/camera.hpp +++ b/src/camera.hpp @@ -7,24 +7,40 @@ namespace lost { class Camera { public: - Camera() = default; - Camera(float xFov, int xResolution, int yResolution) - : xFov(xFov), xResolution(xResolution), yResolution(yResolution) { }; + // Takes focal lengths in pixels + Camera(float xFocalLength, float yFocalLength, + float xCenter, float yCenter, + int xResolution, int yResolution) + : xFocalLength(xFocalLength), yFocalLength(yFocalLength), + xCenter(xCenter), yCenter(yCenter), + xResolution(xResolution), yResolution(yResolution) { }; + Camera(float xFocalLength, int xResolution, int yResolution) + : Camera(xFocalLength, xFocalLength, + xResolution/(float)2.0, yResolution/(float)2.0, + xResolution, yResolution) { }; // Converts from a 3D point in space to a 2D point on the camera sensor. Assumes that X is the // depth direction and that it points away from the center of the sensor, i.e., any vector (x, // 0, 0) will be at (xResolution/2, yResolution/2) on the sensor. - Vec2 ConvertCoordinates(const Vec3 &vector) const; + Vec2 SpatialToCamera(const Vec3 &) const; + // Gives /a/ point in 3d space that could correspond to the given vector, using the same + // coordinate system described for SpatialToCamera. Not all vectors returned by this function + // will necessarily have the same magnitude. + Vec3 CameraToSpatial(const Vec2 &) const; // converts from a 2d point in the camera sensor to right ascension and declination relative to // the center of the camera. - void CoordinateAngles(const Vec2 &vector, float *ra, float *de) const; + // void CoordinateAngles(const Vec2 &vector, float *ra, float *de) const; // returns whether a given pixel is actually in the camera's field of view bool InSensor(const Vec2 &vector) const; - float xFov; - int xResolution; - int yResolution; + int GetXResolution() const; + int GetYResolution() const; + +private: // TODO: distortion + float xFocalLength; float yFocalLength; + float xCenter; float yCenter; + int xResolution; int yResolution; }; } diff --git a/src/io.cpp b/src/io.cpp index aee138a8..9e8e2435 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -93,7 +93,7 @@ std::vector BscParse(std::string tsvPath) { #define DEFAULT_BSC_PATH "bright-star-catalog.tsv" #endif -std::vector CatalogRead() { +std::vector &CatalogRead() { static bool readYet = false; static std::vector catalog; @@ -380,27 +380,23 @@ GeneratedPipelineInput::GeneratedPipelineInput(const Catalog &catalog, Camera camera, int referenceBrightness, float brightnessDeviation, - float noiseDeviation) { - this->attitude = attitude; - this->camera = camera; - image.width = camera.xResolution; - image.height = camera.yResolution; + float noiseDeviation) + : camera(camera), attitude(attitude) { + + image.width = camera.GetXResolution(); + image.height = camera.GetYResolution(); unsigned char *imageRaw = (unsigned char *)calloc(image.width * image.height, 1); imageData = std::unique_ptr(imageRaw); image.image = imageData.get(); for (int i = 0; i < (int)catalog.size(); i++) { const CatalogStar &catalogStar = catalog[i]; - Vec3 spatial = { - cos(catalogStar.raj2000)*cos(catalogStar.dej2000), - sin(catalogStar.raj2000)*cos(catalogStar.dej2000), - sin(catalogStar.dej2000), - }; + Vec3 spatial = SphericalToSpatial(catalog[i].raj2000, catalog[i].dej2000); Vec3 rotated = attitude.Rotate(spatial); if (rotated.x < 0) { continue; } - Vec2 camCoords = camera.ConvertCoordinates(rotated); + Vec2 camCoords = camera.SpatialToCamera(rotated); float radiusX = 3.0; // TODO if (camera.InSensor(camCoords)) { @@ -450,6 +446,7 @@ PipelineInputList PromptGeneratedPipelineInput() { int xResolution = Prompt("Horizontal Resolution"); int yResolution = Prompt("Vertical Resolution"); float xFovDeg = Prompt("Horizontal FOV (in degrees)"); + float xFocalLength = xResolution / (float)2.0 / tan(DegToRad(xFovDeg)/2); int referenceBrightness = Prompt("Reference star brightness"); float brightnessDeviation = Prompt("Star spread stddev"); float noiseDeviation = Prompt("Noise stddev"); @@ -463,7 +460,7 @@ PipelineInputList PromptGeneratedPipelineInput() { GeneratedPipelineInput *curr = new GeneratedPipelineInput( CatalogRead(), attitude, - Camera(DegToRad(xFovDeg), xResolution, yResolution), + Camera(xFocalLength, xResolution, yResolution), referenceBrightness, brightnessDeviation, noiseDeviation); result.push_back(std::unique_ptr(curr)); diff --git a/src/io.hpp b/src/io.hpp index 2ba0c703..26eccef1 100644 --- a/src/io.hpp +++ b/src/io.hpp @@ -114,7 +114,7 @@ class PromptedOutputStream { void WithOutputStream(void (*)(std::ostream *)); // use the environment variable LOST_BSC_PATH, or read from ./bright-star-catalog.tsv -std::vector CatalogRead(); +std::vector &CatalogRead(); // Convert a cairo surface to array of grayscale bytes unsigned char *SurfaceToGrayscaleImage(cairo_surface_t *cairoSurface); cairo_surface_t *GrayscaleImageToSurface(const unsigned char *, const int width, const int height); diff --git a/src/star-id.cpp b/src/star-id.cpp index 76ce0968..b0058828 100644 --- a/src/star-id.cpp +++ b/src/star-id.cpp @@ -26,17 +26,16 @@ StarIdentifiers GeometricVotingStarIdAlgorithm::Go( KVectorDatabase vectorDatabase(database); StarIdentifiers identified; for (int i = 0; i < (int)stars.size(); i++) { - std::vector votes(catalog.size()); - float ra1, de1; - camera.CoordinateAngles({ stars[i].x, stars[i].y }, &ra1, &de1); + std::vector votes(catalog.size(), 0); + // TODO: store spatial coordinates in catalog to avoid this conversion + Vec3 iSpatial = camera.CameraToSpatial({ stars[i].x, stars[i].y }); for (int j = 0; j < (int)stars.size(); j++) { if (i != j) { - float ra2, de2; - camera.CoordinateAngles({ stars[j].x, stars[j].y }, &ra2, &de2); - float gcd = GreatCircleDistance(ra1, de1, ra2, de2); + Vec3 jSpatial = camera.CameraToSpatial({ stars[j].x, stars[j].y }); + float greatCircleDistance = Angle(iSpatial, jSpatial); //give a greater range for min-max Query for bigger radius (GreatCircleDistance) - float lowerBoundRange = gcd - tolerance; - float upperBoundRange = gcd + tolerance; + float lowerBoundRange = greatCircleDistance - tolerance; + float upperBoundRange = greatCircleDistance + tolerance; //if database is a KVectorDatabase long numReturnedPairs; int16_t *lowerBoundSearch = vectorDatabase.FindPossibleStarPairsApprox( @@ -54,7 +53,7 @@ StarIdentifiers GeometricVotingStarIdAlgorithm::Go( // Find star w most votes int16_t maxVotes = votes[0]; int indexOfMax = 0; - for (int v = 0; v < (int)votes.size(); v++) { + for (int v = 1; v < (int)votes.size(); v++) { if (votes[v] > maxVotes) { maxVotes = votes[v]; indexOfMax = v; @@ -70,7 +69,7 @@ StarIdentifiers GeometricVotingStarIdAlgorithm::Go( // // Do we have a metric for localization uncertainty? Star brighntess? //loop i from 1 through n - std::vector verificationVotes(identified.size()); + std::vector verificationVotes(identified.size(), 0); for (int i = 0; i < (int)identified.size(); i++) { //loop j from i+1 through n for (int j = i + 1; j < (int)identified.size(); j++) { @@ -81,11 +80,9 @@ StarIdentifiers GeometricVotingStarIdAlgorithm::Go( Star firstIdentified = stars[identified[i].starIndex]; Star secondIdentified = stars[identified[j].starIndex]; - float ra1, de1; - camera.CoordinateAngles({firstIdentified.x, firstIdentified.y}, &ra1, &de1); - float ra2, de2; - camera.CoordinateAngles({secondIdentified.x, secondIdentified.y }, &ra2, &de2); - float sDist = GreatCircleDistance(ra1, de1, ra2, de2); + Vec3 firstSpatial = camera.CameraToSpatial({firstIdentified.x, firstIdentified.y}); + Vec3 secondSpatial = camera.CameraToSpatial({secondIdentified.x, secondIdentified.y}); + float sDist = Angle(firstSpatial, secondSpatial); //if sDist is in the range of (distance between stars in the image +- R) //add a vote for the match @@ -97,7 +94,7 @@ StarIdentifiers GeometricVotingStarIdAlgorithm::Go( } // Find star w most votes int16_t maxVotes = verificationVotes[0]; - for (int v = 0; v < (int)verificationVotes.size(); v++) { + for (int v = 1; v < (int)verificationVotes.size(); v++) { if (verificationVotes[v] > maxVotes) { maxVotes = verificationVotes[v]; } diff --git a/src/star-utils.hpp b/src/star-utils.hpp index b94384c2..dff9a005 100644 --- a/src/star-utils.hpp +++ b/src/star-utils.hpp @@ -23,6 +23,8 @@ class Star { Star(float x, float y, float radiusX) : Star(x, y, radiusX, radiusX, 0) { }; Star() : Star(0.0, 0.0, 0.0) { }; + // TODO: store a Vec2 instead and then simply point to that Vec2 when passing Star to + // CameraToSpatial? float x; // pixels*10^6 float y; // pixels*10^6 float radiusX; diff --git a/test/geometry.cpp b/test/geometry.cpp new file mode 100644 index 00000000..b6e79710 --- /dev/null +++ b/test/geometry.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include + +#include "camera.hpp" +#include "attitude-utils.hpp" + +using namespace lost; + +TEST_CASE("Convert coordinates: pixel -> spatial -> pixel", "[camera]") { + Camera camera(100, 512, 1024); + + float expectedX = GENERATE(142, 90, 512, 255); + float expectedY = GENERATE(18, 512, 0, 800); + + Vec3 spatial = camera.CameraToSpatial({expectedX, expectedY}); + Vec2 actualPixels = camera.SpatialToCamera(spatial); + + CHECK((int)round(actualPixels.x) == expectedX); + CHECK((int)round(actualPixels.y) == expectedY); +} + +TEST_CASE("Convert coordinates explicit: pixel -> spatial", "[camera]") { + Camera camera(100, 512, 1024); + + Vec3 spatial = camera.CameraToSpatial({300, 728}).Normalize(); + CHECK(spatial.x == Approx(0.413).margin(0.001)); + CHECK(spatial.y == Approx(-0.182).margin(0.001)); + CHECK(spatial.z == Approx(-0.892).margin(0.001)); +} diff --git a/test/kvector.cpp b/test/kvector.cpp new file mode 100644 index 00000000..2fdc6ced --- /dev/null +++ b/test/kvector.cpp @@ -0,0 +1,32 @@ +#include + +#include "databases.hpp" +#include "io.hpp" +#include "attitude-utils.hpp" + +using namespace lost; + +TEST_CASE("Kvector basics: Create, verify that all stars are actually in range.") { + long length; + Catalog &catalog = CatalogRead(); + unsigned char *dbBytes = BuildKVectorDatabase(catalog, &length, 1.0 * M_PI/180.0, 2.0 * M_PI/180.0, 100); + KVectorDatabase db(dbBytes); + REQUIRE(length < 999999); + + long lastNumReturnedPairs = 999999; + for (float i = 1.1; i < 1.99; i += 0.1) { + long numReturnedPairs; + int16_t *pairs = db.FindPossibleStarPairsApprox(i, 1.0, &numReturnedPairs); + for (long k = 0; k < numReturnedPairs; k++) { + float distance = GreatCircleDistance( + catalog[pairs[k]].raj2000, catalog[pairs[k]].dej2000, + catalog[pairs[k+1]].raj2000, catalog[pairs[k]].dej2000); + REQUIRE(i<=distance); + REQUIRE(distance<=2.01); + } + REQUIRE(0 < numReturnedPairs); + REQUIRE(numReturnedPairs < lastNumReturnedPairs); + lastNumReturnedPairs = numReturnedPairs; + delete pairs; + } +} diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 00000000..b3143fbb --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include diff --git a/vendor/catch.hpp b/vendor/catch.hpp new file mode 100644 index 00000000..0384171a --- /dev/null +++ b/vendor/catch.hpp @@ -0,0 +1,17881 @@ +/* + * Catch v2.13.4 + * Generated: 2020-12-29 14:48:00.116107 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 4 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +// We have to avoid both ICC and Clang, because they try to mask themselves +// as gcc, and we want only GCC in this block +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if __cpp_lib_byte > 0 + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template