From ef87c38fdf5075e81a39f2f85a3455f8f11288b3 Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:29:47 -0600 Subject: [PATCH 01/14] add derivation for lobber, fix CMakeUserPresets.json --- flake.nix | 51 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/flake.nix b/flake.nix index d423417..dd87f95 100644 --- a/flake.nix +++ b/flake.nix @@ -49,6 +49,28 @@ cmake --install build --prefix $out ''; }; + lobber = pkgs.stdenv.mkDerivation { + name = "lobber"; + src = self; + nativeBuildInputs = with pkgs; [ + cmake + nlohmann_json + ]; + configurePhase = '' + cmake -S . -B build \ + -D CMAKE_BUILD_TYPE=Release \ + -D LOB_DEVELOPER_MODE=OFF \ + -D BUILD_EXAMPLES=ON \ + -D BUILD_BENCHMARKS=OFF + ''; + buildPhase = '' + cmake --build build --parallel $NIX_BUILD_CORES + ''; + installPhase = '' + mkdir -p $out/bin + cp build/example/lobber $out/bin/ + ''; + }; }); devShells = forEachSupportedSystem ({pkgs}: let baseShell = @@ -101,13 +123,15 @@ buildInputs = oldAttrs.buildInputs ++ extraDevPackages; shellHook = let inherit (pkgs) stdenv; - filename = "CMakeUserPresets.json"; + clangdFile = ".clangd"; + CMakeUserPresetsFile = "CMakeUserPresets.json"; os = if stdenv.isLinux then "linux" else if stdenv.isDarwin then "darwin" else ""; + sourceDir = "\\\${sourceDir}"; in '' json=$(cat <<-EOF @@ -121,7 +145,7 @@ "configurePresets": [ { "name": "dev", - "binaryDir": "/build/dev", + "binaryDir": "${sourceDir}/build/dev", "inherits": ["dev-mode", "ci-${os}"], "generator": "Ninja", "environment": { @@ -130,7 +154,7 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", - "CMAKE_CXX_FLAGS": "$env{CXX_FLAGS_DEV_LINUX} $env{LOB_CXX_FLAGS_COMMON}", + "CMAKE_CXX_FLAGS": "\$env{CXX_FLAGS_DEV_LINUX} \$env{LOB_CXX_FLAGS_COMMON}", "CMAKE_LINKER_TYPE": "MOLD" } } @@ -161,11 +185,24 @@ EOF ) - if [ ! -f ${filename} ]; then - echo "$json" > ${filename} - echo "${filename} created successfully" + clangd=$(cat <<-EOF + CompileFlags: + CompilationDatabase: build/dev + EOF + ) + + if [ ! -f ${CMakeUserPresetsFile} ]; then + echo "$json" > ${CMakeUserPresetsFile} + echo "${CMakeUserPresetsFile} created successfully" + else + echo "${CMakeUserPresetsFile} already exists" + fi + + if [ ! -f ${clangdFile} ]; then + echo "$clangd" > ${clangdFile} + echo "${clangdFile} created successfully" else - echo "${filename} already exists" + echo "${clangdFile} already exists" fi '' + oldAttrs.shellHook; From 6d043120b0fa84d694147daf4618c2ffe674559d Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:31:10 -0600 Subject: [PATCH 02/14] rework error handling --- CMakeLists.txt | 2 +- include/lob/lob.hpp | 52 +++-- source/lob_builder.cpp | 329 ++++++++++++++++++------------- test/source/lob_builder_test.cpp | 83 ++++---- 4 files changed, 267 insertions(+), 199 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba4faf6..e372576 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(cmake/prelude.cmake) project( lob - VERSION 0.6.5 + VERSION 0.7.0 DESCRIPTION "an exterior balistics calculation library" HOMEPAGE_URL "https://github.com/joelbenway/lob" LANGUAGES CXX) diff --git a/include/lob/lob.hpp b/include/lob/lob.hpp index 699427a..b473268 100644 --- a/include/lob/lob.hpp +++ b/include/lob/lob.hpp @@ -62,27 +62,31 @@ enum class LOB_EXPORT ClockAngleT : uint8_t { enum class LOB_EXPORT ErrorT : uint8_t { kNone, - kAirPressure, - kAltitude, - kAzimuth, - kBallisticCoefficient, - kBaseDiameter, - kDiameter, - kHumidity, - kInitialVelocity, - kLatitude, - kLength, - kMachDragTable, - kMass, - kMaximumTime, - kMeplatDiameter, - kNoseLength, - kOgiveRtR, - kRangeAngle, - kTailLength, - kWindHeading, - kZeroAngle, - kZeroDistance, + kAirPressureOOR, + kAltitudeOfBarometerOOR, + kAltitudeOfFiringSiteOOR, + kAltitudeOfThermometerOOR, + kAzimuthOOR, + kBallisticCoefficientOOR, + kBallisticCoefficientRequired, + kBaseDiameterOOR, + kDiameterOOR, + kHumidityOOR, + kInitialVelocityRequired, + kInternalError, + kLatitudeOOR, + kLengthOOR, + kMassOOR, + kMaximumTimeOOR, + kMeplatDiameterOOR, + kNoseLengthOOR, + kOgiveRtROOR, + kRangeAngleOOR, + kTailLengthOOR, + kWindHeadingOOR, + kZeroAngleOOR, + kZeroDataRequired, + kZeroDistanceOOR, kNotFormed }; // enum class ErrorT @@ -437,12 +441,6 @@ class LOB_EXPORT Builder { */ Builder& Reset() noexcept; - /** - * @brief Checks if the current builder state is well-formed. - * @return True if state is valid, false otherwise. - */ - bool IsValid() const; - /** * @brief Builds the `Input` object with the configured parameters. * @return The constructed `Input` object. diff --git a/source/lob_builder.cpp b/source/lob_builder.cpp index 51bd57b..f249c04 100644 --- a/source/lob_builder.cpp +++ b/source/lob_builder.cpp @@ -100,67 +100,36 @@ Builder& Builder::operator=(Builder&& rhs) noexcept { } Builder& Builder::AltitudeOfFiringSiteFt(double value) { - const bool kIsValid = (-kIsaStratosphereAltitudeFt < value) && - (value < kIsaStratosphereAltitudeFt); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kAltitude; - } pimpl_->altitude_ft = FeetT(value); return *this; } Builder& Builder::AltitudeOfBarometerFt(double value) { - const bool kIsValid = (-kIsaStratosphereAltitudeFt < value) && - (value < kIsaStratosphereAltitudeFt); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kAltitude; - } pimpl_->altitude_of_barometer_ft = FeetT(value); return *this; } Builder& Builder::AltitudeOfThermometerFt(double value) { - const bool kIsValid = (-kIsaStratosphereAltitudeFt < value) && - (value < kIsaStratosphereAltitudeFt); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kAltitude; - } pimpl_->altitude_of_thermometer_ft = FeetT(value); return *this; } Builder& Builder::AzimuthDeg(double value) { - const bool kIsValid = (-kDegreesPerTurn < value) && (value < kDegreesPerTurn); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kAzimuth; - } pimpl_->azimuth_rad = DegreesT(value); return *this; } Builder& Builder::BallisticCoefficientPsi(double value) { - const bool kIsValid = (0.0 < value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kBallisticCoefficient; - } pimpl_->ballistic_coefficient_psi = PmsiT(value); return *this; } Builder& Builder::AirPressureInHg(double value) { - const bool kIsValid = (0.0 < value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kAirPressure; - } pimpl_->air_pressure_in_hg = InHgT(value); return *this; } Builder& Builder::BaseDiameterInch(double value) { - const bool kIsValid = (0.0 <= value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kBaseDiameter; - } pimpl_->base_diameter_in = InchT(value); return *this; } @@ -204,37 +173,21 @@ Builder& Builder::BCDragFunction(DragFunctionT type) { } Builder& Builder::DiameterInch(double value) { - const bool kIsValid = (0.0 < value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kDiameter; - } pimpl_->diameter_in = InchT(value); return *this; } Builder& Builder::InitialVelocityFps(uint16_t value) { - const bool kIsValid = (0 != value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kInitialVelocity; - } pimpl_->build.velocity = FpsT(value).U16(); return *this; } Builder& Builder::LatitudeDeg(double value) { - const bool kIsValid = (-90.0 <= value && value <= 90.0); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kLatitude; - } pimpl_->latitude_rad = DegreesT(value); return *this; } Builder& Builder::LengthInch(double value) { - const bool kIsValid = (0 < value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kLength; - } pimpl_->length_in = InchT(value); return *this; } @@ -242,7 +195,6 @@ Builder& Builder::LengthInch(double value) { Builder& Builder::MachVsDragTable(const float* pmachs, const float* pdrags, size_t size) { if (pmachs == nullptr || pdrags == nullptr || size == 0) { - pimpl_->build.error = ErrorT::kMachDragTable; return *this; } for (size_t i = 0; i < kTableSize; i++) { @@ -257,28 +209,16 @@ Builder& Builder::MachVsDragTable(const float* pmachs, const float* pdrags, } Builder& Builder::MassGrains(double value) { - const bool kIsValid = (0.0 < value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kMass; - } pimpl_->build.mass = LbsT(GrainT(value)).Value(); return *this; } Builder& Builder::MaximumTime(double value) { - const bool kIsValid = (0.0 < value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kMaximumTime; - } pimpl_->build.max_time = value; return *this; } Builder& Builder::MeplatDiameterInch(double value) { - const bool kIsValid = (0.0 <= value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kMeplatDiameter; - } pimpl_->meplat_diameter_in = InchT(value); return *this; } @@ -294,19 +234,11 @@ Builder& Builder::MinimumSpeed(uint16_t value) { } Builder& Builder::NoseLengthInch(double value) { - const bool kIsValid = (0.0 <= value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kNoseLength; - } pimpl_->nose_length_in = InchT(value); return *this; } Builder& Builder::OgiveRtR(double value) { - const bool kIsValid = (0.0 <= value && value <= 1.0); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kOgiveRtR; - } pimpl_->ogive_rtr = value; return *this; } @@ -317,19 +249,11 @@ Builder& Builder::OpticHeightInches(double value) { } Builder& Builder::RelativeHumidityPercent(double value) { - const bool kIsValid = (0.0 <= value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kHumidity; - } pimpl_->relative_humidity_percent = value; return *this; } Builder& Builder::RangeAngleDeg(double value) { - const bool kIsValid = (-90.0 < value && value < 90.0); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kRangeAngle; - } pimpl_->range_angle_rad = RadiansT(DegreesT(value)); return *this; } @@ -340,10 +264,6 @@ Builder& Builder::StepSize(uint16_t value) { } Builder& Builder::TailLengthInch(double value) { - const bool kIsValid = (0.0 <= value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kTailLength; - } pimpl_->tail_length_in = InchT(value); return *this; } @@ -374,10 +294,6 @@ Builder& Builder::WindHeadingDeg(double value) { const DegreesT kFullTurn(kDegreesPerTurn); const DegreesT kQuarterTurn(kFullTurn / 4); DegreesT angle(value); - const bool kIsValid = (kFullTurn * -1 < angle && angle < kFullTurn); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kWindHeading; - } angle = angle * -1 + kQuarterTurn; @@ -401,19 +317,11 @@ Builder& Builder::WindSpeedMph(double value) { } Builder& Builder::ZeroAngleMOA(double value) { - const bool kIsInvalid = (-45.0 > value || value > 45.0); - if (kIsInvalid) { - pimpl_->build.error = ErrorT::kZeroAngle; - } pimpl_->build.zero_angle = MoaT(value).Value(); return *this; } Builder& Builder::ZeroDistanceYds(double value) { - const bool kIsValid = (0.0 < value); - if (!kIsValid) { - pimpl_->build.error = ErrorT::kZeroDistance; - } pimpl_->zero_distance_ft = YardT(value); return *this; } @@ -429,18 +337,8 @@ Builder& Builder::Reset() noexcept { return *this; } -bool Builder::IsValid() const { - const bool kErrorIsInExpectedState = - pimpl_->build.error == ErrorT::kNotFormed || - pimpl_->build.error == ErrorT::kNone; - const bool kBCisOk = !pimpl_->ballistic_coefficient_psi.IsNaN(); - const bool kVelocityIsOk = pimpl_->build.velocity > 0; - const bool kZeroIsOk = !pimpl_->zero_distance_ft.IsNaN() || - !std::isnan(pimpl_->build.zero_angle); - return kErrorIsInExpectedState && kBCisOk && kVelocityIsOk && kZeroIsOk; -} - namespace { + void BuildEnvironment(Impl* pimpl) { assert(pimpl != nullptr); FeetT altitude_of_firing_site = FeetT(0); @@ -454,6 +352,13 @@ void BuildEnvironment(Impl* pimpl) { pimpl->range_angle_rad = RadiansT(DegreesT(0)); } + const bool kRangeAngleValid = pimpl->range_angle_rad > DegreesT(-90.0) && + pimpl->range_angle_rad < DegreesT(90.0); + if (!kRangeAngleValid) { + pimpl->build.error = ErrorT::kRangeAngleOOR; + return; + } + pimpl->build.gravity.x = kStandardGravityFtPerSecSq * -1 * std::sin(pimpl->range_angle_rad.Value()); pimpl->build.gravity.y = kStandardGravityFtPerSecSq * -1 * @@ -468,6 +373,26 @@ void BuildEnvironment(Impl* pimpl) { ? pimpl->altitude_ft : pimpl->altitude_of_thermometer_ft; + auto is_altitude_valid = [](FeetT altitude) -> bool { + return FeetT(-kIsaStratosphereAltitudeFt) < altitude && + altitude < FeetT(kIsaStratosphereAltitudeFt); + }; + + if (!is_altitude_valid(altitude_of_firing_site)) { + pimpl->build.error = ErrorT::kAltitudeOfFiringSiteOOR; + return; + } + + if (!is_altitude_valid(altitude_of_barometer)) { + pimpl->build.error = ErrorT::kAltitudeOfBarometerOOR; + return; + } + + if (!is_altitude_valid(altitude_of_thermometer)) { + pimpl->build.error = ErrorT::kAltitudeOfThermometerOOR; + return; + } + temperature_at_firing_site = CalculateTemperatureAtAltitude( altitude_of_firing_site, DegFT(kIsaSeaLevelDegF)); pressure_at_firing_site = BarometricFormula(altitude_of_firing_site, @@ -485,6 +410,10 @@ void BuildEnvironment(Impl* pimpl) { } if (!std::isnan(pimpl->air_pressure_in_hg)) { + if (pimpl->air_pressure_in_hg < InHgT(0.0)) { + pimpl->build.error = ErrorT::kAirPressureOOR; + return; + } pressure_at_firing_site = BarometricFormula(altitude_of_firing_site - altitude_of_barometer, pimpl->air_pressure_in_hg, temperature_at_barometer); @@ -494,6 +423,10 @@ void BuildEnvironment(Impl* pimpl) { pimpl->relative_humidity_percent = kIsaSeaLevelHumidityPercent; } + if (pimpl->relative_humidity_percent < 0.0) { + pimpl->build.error = ErrorT::kHumidityOOR; + } + const auto kWaterVaporSaturationPressureInHg = CalculateWaterVaporSaturationPressure(temperature_at_firing_site); @@ -514,9 +447,18 @@ void BuildEnvironment(Impl* pimpl) { void BuildTable(Impl* pimpl) { assert(pimpl != nullptr); - assert(!std::isnan(pimpl->ballistic_coefficient_psi)); assert(!std::isnan(pimpl->air_density_lbs_per_cu_ft)); + if (pimpl->ballistic_coefficient_psi.IsNaN()) { + pimpl->build.error = ErrorT::kBallisticCoefficientRequired; + return; + } + + if (pimpl->ballistic_coefficient_psi <= PmsiT(0.0)) { + pimpl->build.error = ErrorT::kBallisticCoefficientOOR; + return; + } + if (pimpl->atmosphere_reference == AtmosphereReferenceT::kArmyStandardMetro) { pimpl->ballistic_coefficient_psi *= kArmyToIcaoBcConversionFactor; pimpl->atmosphere_reference = AtmosphereReferenceT::kIcao; @@ -535,10 +477,18 @@ void BuildTable(Impl* pimpl) { void BuildWind(Impl* pimpl) { assert(pimpl != nullptr); + if (std::isnan(pimpl->wind_heading_rad)) { pimpl->wind_heading_rad = DegreesT(0); } + const DegreesT kFullTurn(kDegreesPerTurn); + if (pimpl->wind_heading_rad > kFullTurn || + pimpl->wind_heading_rad < kFullTurn * -1) { + pimpl->build.error = ErrorT::kWindHeadingOOR; + return; + } + if (std::isnan(pimpl->wind_speed_fps)) { pimpl->wind_speed_fps = FpsT(0); } @@ -551,28 +501,82 @@ void BuildWind(Impl* pimpl) { .Value(); } +void BuildOpticHeight(Impl* pimpl) { + assert(pimpl != nullptr); + if (std::isnan(pimpl->build.optic_height)) { + constexpr FeetT kDefaultOpticHeight = InchT(1.5); + pimpl->build.optic_height = kDefaultOpticHeight.Value(); + } +} + void BuildStability(Impl* pimpl) { assert(pimpl != nullptr); - assert(pimpl->build.velocity > 0); assert(!std::isnan(pimpl->air_density_lbs_per_cu_ft)); - if ((pimpl->diameter_in > InchT(0)) && (pimpl->length_in > InchT(0)) && - !AreEqual(pimpl->twist_inches_per_turn, InchPerTwistT(0)) && - !std::isnan(pimpl->build.mass)) { - const double kFtp = CalculateMillerTwistRuleCorrectionFactor( - pimpl->air_density_lbs_per_cu_ft); - pimpl->build.stability_factor = - kFtp * CalculateMillerTwistRuleStabilityFactor( - pimpl->diameter_in, GrainT(LbsT(pimpl->build.mass)), - pimpl->length_in, pimpl->twist_inches_per_turn, - FpsT(pimpl->build.velocity)); + if (std::isnan(pimpl->build.velocity) || pimpl->build.velocity <= 0) { + pimpl->build.error = ErrorT::kInitialVelocityRequired; + return; } + + if (pimpl->diameter_in <= InchT(0)) { + pimpl->build.error = ErrorT::kDiameterOOR; + return; + } + + if (pimpl->length_in <= InchT(0)) { + pimpl->build.error = ErrorT::kLengthOOR; + return; + } + + if (pimpl->build.mass <= 0) { + pimpl->build.error = ErrorT::kMassOOR; + return; + } + + if (pimpl->diameter_in.IsNaN() || pimpl->length_in.IsNaN() || + std::isnan(pimpl->build.mass) || pimpl->twist_inches_per_turn.IsNaN() || + AreEqual(pimpl->twist_inches_per_turn, InchPerTwistT(0))) { + return; + } + + const double kFtp = CalculateMillerTwistRuleCorrectionFactor( + pimpl->air_density_lbs_per_cu_ft); + pimpl->build.stability_factor = + kFtp * CalculateMillerTwistRuleStabilityFactor( + pimpl->diameter_in, GrainT(LbsT(pimpl->build.mass)), + pimpl->length_in, pimpl->twist_inches_per_turn, + FpsT(pimpl->build.velocity)); } void BuildBoatright(Impl* pimpl) { assert(pimpl != nullptr); assert(pimpl->pdrag_lut != nullptr); + if (pimpl->meplat_diameter_in < InchT(0)) { + pimpl->build.error = ErrorT::kMeplatDiameterOOR; + return; + } + + if (pimpl->base_diameter_in <= InchT(0)) { + pimpl->build.error = ErrorT::kBaseDiameterOOR; + return; + } + + if (pimpl->nose_length_in < InchT(0)) { + pimpl->build.error = ErrorT::kNoseLengthOOR; + return; + } + + if (pimpl->tail_length_in < InchT(0)) { + pimpl->build.error = ErrorT::kTailLengthOOR; + return; + } + + if (pimpl->ogive_rtr < 0 || pimpl->ogive_rtr > 1.0) { + pimpl->build.error = ErrorT::kOgiveRtROOR; + return; + } + const InchT kD(pimpl->diameter_in); const CaliberT kDM(pimpl->meplat_diameter_in, kD.Inverse()); const CaliberT kDB(pimpl->base_diameter_in, kD.Inverse()); @@ -698,7 +702,20 @@ void BuildLitzAerodynamicJump(Impl* pimpl) { void BuildCoriolis(Impl* pimpl) { assert(pimpl != nullptr); + if (!std::isnan(pimpl->azimuth_rad) && !std::isnan(pimpl->latitude_rad)) { + const DegreesT kAzimuthLimit(kDegreesPerTurn); + if (pimpl->azimuth_rad > kAzimuthLimit || + pimpl->azimuth_rad < kAzimuthLimit * -1) { + pimpl->build.error = ErrorT::kAzimuthOOR; + return; + } + const DegreesT kLatitudeLimit(90); + if (pimpl->latitude_rad > kLatitudeLimit || + pimpl->latitude_rad < kLatitudeLimit * -1) { + pimpl->build.error = ErrorT::kLatitudeOOR; + return; + } // Coriolis Effect Page 178 of Modern Exterior Ballistics - McCoy const double kCosL = std::cos(pimpl->latitude_rad).Value(); const double kSinA = std::sin(pimpl->azimuth_rad).Value(); @@ -719,11 +736,26 @@ void BuildCoriolis(Impl* pimpl) { void BuildZeroAngle(Impl* pimpl) { assert(pimpl != nullptr); + if (!std::isnan(pimpl->build.zero_angle)) { + const double kZeroAngleLimit = MoaT(DegreesT(45)).Value(); + if (pimpl->build.zero_angle > kZeroAngleLimit || + pimpl->build.zero_angle < kZeroAngleLimit * -1) { + pimpl->build.error = ErrorT::kZeroAngleOOR; + } + return; + } + + if (pimpl->zero_distance_ft.IsNaN()) { + pimpl->build.error = ErrorT::kZeroDataRequired; + return; + } + + if (pimpl->zero_distance_ft <= FeetT(0)) { + pimpl->build.error = ErrorT::kZeroDistanceOOR; return; } - assert(!std::isnan(pimpl->zero_distance_ft)); assert(pimpl->build.velocity > 0); assert(!std::isnan(pimpl->build.aerodynamic_jump)); @@ -772,6 +804,12 @@ void BuildZeroAngle(Impl* pimpl) { void BuildOptions(Impl* pimpl) { assert(pimpl != nullptr); + + if (pimpl->build.max_time <= 0.0) { + pimpl->build.error = ErrorT::kMaximumTimeOOR; + return; + } + const FpsT kMinSpeed = CalculateVelocityFromKineticEnergy( pimpl->minimum_energy_ft_lbs, SlugT(LbsT(pimpl->build.mass))); pimpl->build.minimum_speed = @@ -781,24 +819,47 @@ void BuildOptions(Impl* pimpl) { } // namespace Input Builder::Build() { - if (pimpl_->build.error == ErrorT::kNone) { - pimpl_->build.error = ErrorT::kNotFormed; - } - if (IsValid()) { - // This order matters - BuildEnvironment(pimpl_); - BuildTable(pimpl_); - BuildWind(pimpl_); - if (std::isnan(pimpl_->build.optic_height)) { - constexpr FeetT kDefaultOpticHeight = InchT(1.5); - pimpl_->build.optic_height = kDefaultOpticHeight.Value(); - } - BuildStability(pimpl_); - BuildBoatright(pimpl_); - BuildLitzAerodynamicJump(pimpl_); - BuildCoriolis(pimpl_); - BuildZeroAngle(pimpl_); - BuildOptions(pimpl_); + pimpl_->build.error = ErrorT::kNotFormed; + // This order matters + BuildEnvironment(pimpl_); + if (pimpl_->build.error != ErrorT::kNotFormed) { + return pimpl_->build; + } + BuildTable(pimpl_); + if (pimpl_->build.error != ErrorT::kNotFormed) { + return pimpl_->build; + } + BuildWind(pimpl_); + if (pimpl_->build.error != ErrorT::kNotFormed) { + return pimpl_->build; + } + BuildOpticHeight(pimpl_); + if (pimpl_->build.error != ErrorT::kNotFormed) { + return pimpl_->build; + } + BuildStability(pimpl_); + if (pimpl_->build.error != ErrorT::kNotFormed) { + return pimpl_->build; + } + BuildBoatright(pimpl_); + if (pimpl_->build.error != ErrorT::kNotFormed) { + return pimpl_->build; + } + BuildLitzAerodynamicJump(pimpl_); + if (pimpl_->build.error != ErrorT::kNotFormed) { + return pimpl_->build; + } + BuildCoriolis(pimpl_); + if (pimpl_->build.error != ErrorT::kNotFormed) { + return pimpl_->build; + } + BuildZeroAngle(pimpl_); + if (pimpl_->build.error != ErrorT::kNotFormed) { + return pimpl_->build; + } + BuildOptions(pimpl_); + + if (pimpl_->build.error == ErrorT::kNotFormed) { pimpl_->build.error = ErrorT::kNone; } return pimpl_->build; diff --git a/test/source/lob_builder_test.cpp b/test/source/lob_builder_test.cpp index 6f2b036..29fdc12 100644 --- a/test/source/lob_builder_test.cpp +++ b/test/source/lob_builder_test.cpp @@ -129,31 +129,31 @@ TEST_F(BuilderTestFixture, BuildMinimalInput) { EXPECT_DOUBLE_EQ(kResult.gravity.y, -1.0 * lob::kStandardGravityFtPerSecSq); } -TEST_F(BuilderTestFixture, BuildInvalidVelocityInput) { +TEST_F(BuilderTestFixture, BuildMissingVelocityInput) { const double kTestBC = 0.425; const double kTestZeroAngle = 3.84; const lob::Input kResult = puut->BallisticCoefficientPsi(kTestBC) .ZeroAngleMOA(kTestZeroAngle) .Build(); - EXPECT_TRUE(std::isnan(kResult.table_coefficient)); + EXPECT_EQ(kResult.error, lob::ErrorT::kInitialVelocityRequired); } -TEST_F(BuilderTestFixture, BuildInvalidBCInput) { +TEST_F(BuilderTestFixture, BuildMissingBCInput) { const uint16_t kTestMuzzleVelocity = 2700U; const double kTestZeroAngle = 3.84; const lob::Input kResult = puut->InitialVelocityFps(kTestMuzzleVelocity) .ZeroAngleMOA(kTestZeroAngle) .Build(); - EXPECT_TRUE(std::isnan(kResult.table_coefficient)); + EXPECT_EQ(kResult.error, lob::ErrorT::kBallisticCoefficientRequired); } -TEST_F(BuilderTestFixture, BuildInvalidZeroInput) { +TEST_F(BuilderTestFixture, BuildMissingZeroInput) { const double kTestBC = 0.425; const uint16_t kTestMuzzleVelocity = 2700U; const lob::Input kResult = puut->BallisticCoefficientPsi(kTestBC) .InitialVelocityFps(kTestMuzzleVelocity) .Build(); - EXPECT_TRUE(std::isnan(kResult.table_coefficient)); + EXPECT_EQ(kResult.error, lob::ErrorT::kZeroDataRequired); } TEST_F(BuilderTestFixture, BuildG1UsingCustomTable) { @@ -358,9 +358,10 @@ TEST_F(BuilderTestFixture, ResetWorks) { .InitialVelocityFps(kM70MuzzleVelocity) .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) - .Reset() .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kNotFormed); + EXPECT_EQ(kJack.error, lob::ErrorT::kNone); + const lob::Input kReset = puut->Reset().Build(); + EXPECT_TRUE(kReset.error != lob::ErrorT::kNone); } TEST_F(BuilderTestFixture, AirPressureError) { @@ -376,7 +377,7 @@ TEST_F(BuilderTestFixture, AirPressureError) { .ZeroImpactHeightInches(kZeroHeight) .AirPressureInHg(-1.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kAirPressure); + EXPECT_EQ(kJack.error, lob::ErrorT::kAirPressureOOR); } TEST_F(BuilderTestFixture, FiringSiteAltitudeError) { @@ -392,12 +393,13 @@ TEST_F(BuilderTestFixture, FiringSiteAltitudeError) { .ZeroImpactHeightInches(kZeroHeight) .AltitudeOfFiringSiteFt(lob::kIsaStratosphereAltitudeFt + 1) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kAltitude); + EXPECT_EQ(kJack.error, lob::ErrorT::kAltitudeOfFiringSiteOOR); } TEST_F(BuilderTestFixture, BarometerAltitudeError) { const double kSierraGameKingBC = 0.436; const uint16_t kM70MuzzleVelocity = 3100U; + const double kAltitude = 0.0; const double kZeroYardage = 100.0; const double kZeroHeight = 3.0; const lob::Input kJack = @@ -406,14 +408,16 @@ TEST_F(BuilderTestFixture, BarometerAltitudeError) { .InitialVelocityFps(kM70MuzzleVelocity) .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) + .AltitudeOfFiringSiteFt(kAltitude) .AltitudeOfBarometerFt(lob::kIsaStratosphereAltitudeFt + 1) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kAltitude); + EXPECT_EQ(kJack.error, lob::ErrorT::kAltitudeOfBarometerOOR); } TEST_F(BuilderTestFixture, ThermometerAltitudeError) { const double kSierraGameKingBC = 0.436; const uint16_t kM70MuzzleVelocity = 3100U; + const double kAltitude = 0.0; const double kZeroYardage = 100.0; const double kZeroHeight = 3.0; const lob::Input kJack = @@ -422,14 +426,16 @@ TEST_F(BuilderTestFixture, ThermometerAltitudeError) { .InitialVelocityFps(kM70MuzzleVelocity) .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) + .AltitudeOfFiringSiteFt(kAltitude) .AltitudeOfThermometerFt(lob::kIsaStratosphereAltitudeFt + 1) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kAltitude); + EXPECT_EQ(kJack.error, lob::ErrorT::kAltitudeOfThermometerOOR); } TEST_F(BuilderTestFixture, AzimuthError) { const double kSierraGameKingBC = 0.436; const uint16_t kM70MuzzleVelocity = 3100U; + const double kLatitude = 45.0; const double kZeroYardage = 100.0; const double kZeroHeight = 3.0; const lob::Input kJack = @@ -439,8 +445,9 @@ TEST_F(BuilderTestFixture, AzimuthError) { .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) .AzimuthDeg(lob::kDegreesPerTurn + 1) + .LatitudeDeg(kLatitude) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kAzimuth); + EXPECT_EQ(kJack.error, lob::ErrorT::kAzimuthOOR); } TEST_F(BuilderTestFixture, BallisticCoefficientError) { @@ -455,7 +462,7 @@ TEST_F(BuilderTestFixture, BallisticCoefficientError) { .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kBallisticCoefficient); + EXPECT_EQ(kJack.error, lob::ErrorT::kBallisticCoefficientOOR); } TEST_F(BuilderTestFixture, BaseDiameterError) { @@ -471,7 +478,7 @@ TEST_F(BuilderTestFixture, BaseDiameterError) { .ZeroImpactHeightInches(kZeroHeight) .BaseDiameterInch(-1.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kBaseDiameter); + EXPECT_EQ(kJack.error, lob::ErrorT::kBaseDiameterOOR); } TEST_F(BuilderTestFixture, DiameterError) { @@ -487,7 +494,7 @@ TEST_F(BuilderTestFixture, DiameterError) { .ZeroImpactHeightInches(kZeroHeight) .DiameterInch(-1.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kDiameter); + EXPECT_EQ(kJack.error, lob::ErrorT::kDiameterOOR); } TEST_F(BuilderTestFixture, HumidityError) { @@ -503,7 +510,7 @@ TEST_F(BuilderTestFixture, HumidityError) { .ZeroImpactHeightInches(kZeroHeight) .RelativeHumidityPercent(-1.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kHumidity); + EXPECT_EQ(kJack.error, lob::ErrorT::kHumidityOOR); } TEST_F(BuilderTestFixture, InitialVelocity) { @@ -517,12 +524,13 @@ TEST_F(BuilderTestFixture, InitialVelocity) { .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kInitialVelocity); + EXPECT_EQ(kJack.error, lob::ErrorT::kInitialVelocityRequired); } TEST_F(BuilderTestFixture, LatitudeError) { const double kSierraGameKingBC = 0.436; const uint16_t kM70MuzzleVelocity = 3100U; + const double kAzimuth = 0; const double kZeroYardage = 100.0; const double kZeroHeight = 3.0; const lob::Input kJack = @@ -531,9 +539,10 @@ TEST_F(BuilderTestFixture, LatitudeError) { .InitialVelocityFps(kM70MuzzleVelocity) .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) + .AzimuthDeg(kAzimuth) .LatitudeDeg(91.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kLatitude); + EXPECT_EQ(kJack.error, lob::ErrorT::kLatitudeOOR); } TEST_F(BuilderTestFixture, LengthError) { @@ -549,23 +558,21 @@ TEST_F(BuilderTestFixture, LengthError) { .ZeroImpactHeightInches(kZeroHeight) .LengthInch(-1.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kLength); + EXPECT_EQ(kJack.error, lob::ErrorT::kLengthOOR); } -TEST_F(BuilderTestFixture, MachVsDragTableError) { - const double kSierraGameKingBC = 0.436; +TEST_F(BuilderTestFixture, MachVsDragTableBadParamsIgnored) { const uint16_t kM70MuzzleVelocity = 3100U; const double kZeroYardage = 100.0; const double kZeroHeight = 3.0; const lob::Input kJack = - puut->BallisticCoefficientPsi(kSierraGameKingBC) - .BCAtmosphere(lob::AtmosphereReferenceT::kArmyStandardMetro) + puut->BCAtmosphere(lob::AtmosphereReferenceT::kArmyStandardMetro) .InitialVelocityFps(kM70MuzzleVelocity) .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) .MachVsDragTable(nullptr, nullptr, 0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kMachDragTable); + EXPECT_EQ(kJack.error, lob::ErrorT::kBallisticCoefficientRequired); } TEST_F(BuilderTestFixture, MassError) { @@ -581,7 +588,7 @@ TEST_F(BuilderTestFixture, MassError) { .ZeroImpactHeightInches(kZeroHeight) .MassGrains(-1.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kMass); + EXPECT_EQ(kJack.error, lob::ErrorT::kMassOOR); } TEST_F(BuilderTestFixture, MaximumTimeError) { @@ -597,7 +604,7 @@ TEST_F(BuilderTestFixture, MaximumTimeError) { .ZeroImpactHeightInches(kZeroHeight) .MaximumTime(-1.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kMaximumTime); + EXPECT_EQ(kJack.error, lob::ErrorT::kMaximumTimeOOR); } TEST_F(BuilderTestFixture, MeplatDiameterError) { @@ -613,7 +620,7 @@ TEST_F(BuilderTestFixture, MeplatDiameterError) { .ZeroImpactHeightInches(kZeroHeight) .MeplatDiameterInch(-1.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kMeplatDiameter); + EXPECT_EQ(kJack.error, lob::ErrorT::kMeplatDiameterOOR); } TEST_F(BuilderTestFixture, NoseLengthError) { @@ -629,7 +636,7 @@ TEST_F(BuilderTestFixture, NoseLengthError) { .ZeroImpactHeightInches(kZeroHeight) .NoseLengthInch(-1) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kNoseLength); + EXPECT_EQ(kJack.error, lob::ErrorT::kNoseLengthOOR); } TEST_F(BuilderTestFixture, OgiveRtRError) { @@ -645,7 +652,7 @@ TEST_F(BuilderTestFixture, OgiveRtRError) { .ZeroImpactHeightInches(kZeroHeight) .OgiveRtR(-1) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kOgiveRtR); + EXPECT_EQ(kJack.error, lob::ErrorT::kOgiveRtROOR); } TEST_F(BuilderTestFixture, RangeAngleError) { @@ -661,7 +668,7 @@ TEST_F(BuilderTestFixture, RangeAngleError) { .ZeroImpactHeightInches(kZeroHeight) .RangeAngleDeg(90) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kRangeAngle); + EXPECT_EQ(kJack.error, lob::ErrorT::kRangeAngleOOR); } TEST_F(BuilderTestFixture, TailLengthError) { @@ -677,12 +684,13 @@ TEST_F(BuilderTestFixture, TailLengthError) { .ZeroImpactHeightInches(kZeroHeight) .TailLengthInch(-1.0) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kTailLength); + EXPECT_EQ(kJack.error, lob::ErrorT::kTailLengthOOR); } TEST_F(BuilderTestFixture, WindHeadingError) { const double kSierraGameKingBC = 0.436; const uint16_t kM70MuzzleVelocity = 3100U; + const uint16_t kWindSpeed = 10; const double kZeroYardage = 100.0; const double kZeroHeight = 3.0; const lob::Input kJack = @@ -691,9 +699,10 @@ TEST_F(BuilderTestFixture, WindHeadingError) { .InitialVelocityFps(kM70MuzzleVelocity) .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) - .WindHeadingDeg(lob::kDegreesPerTurn + 1) + .WindHeadingDeg(lob::kDegreesPerTurn * 3) + .WindSpeedMph(kWindSpeed) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kWindHeading); + EXPECT_EQ(kJack.error, lob::ErrorT::kWindHeadingOOR); } TEST_F(BuilderTestFixture, ZeroAngleError) { @@ -707,9 +716,9 @@ TEST_F(BuilderTestFixture, ZeroAngleError) { .InitialVelocityFps(kM70MuzzleVelocity) .ZeroDistanceYds(kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) - .ZeroAngleMOA(46.0) + .ZeroAngleMOA(lob::MoaT(lob::DegreesT(46)).Value()) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kZeroAngle); + EXPECT_EQ(kJack.error, lob::ErrorT::kZeroAngleOOR); } TEST_F(BuilderTestFixture, ZeroDistanceError) { @@ -724,7 +733,7 @@ TEST_F(BuilderTestFixture, ZeroDistanceError) { .ZeroDistanceYds(-kZeroYardage) .ZeroImpactHeightInches(kZeroHeight) .Build(); - EXPECT_TRUE(kJack.error == lob::ErrorT::kZeroDistance); + EXPECT_EQ(kJack.error, lob::ErrorT::kZeroDistanceOOR); } TEST_F(BuilderTestFixture, RangeAngleDeg) { From 6325de3453f5f1992787f09f33b8f2068a162b1a Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:34:23 -0600 Subject: [PATCH 03/14] Update lobber --- example/CMakeLists.txt | 2 +- example/lobber.cpp | 57 +++++++++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index a9951df..e29368d 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.14) project( lobExamples - VERSION 1.0.2 + VERSION 1.0.3 DESCRIPTION "Examples using the lob library" HOMEPAGE_URL "https://github.com/joelbenway/lob" LANGUAGES CXX) diff --git a/example/lobber.cpp b/example/lobber.cpp index 6aa9e5a..56bc235 100644 --- a/example/lobber.cpp +++ b/example/lobber.cpp @@ -10,11 +10,18 @@ #include #include #include +#include #include #include #include #include +#ifdef _WIN32 +#include +#else +#include +#endif + #include "lob/lob.hpp" #include "version.hpp" @@ -42,7 +49,7 @@ void PrintVersion() { } void PrintHelp() { - std::cout << "Usage: lobber [options]\n" + std::cout << "Usage: lobber [options] stdin\n" << "Options:\n" << " --h, --help Show this help message\n" << " --v, --version Show version information\n" @@ -63,6 +70,15 @@ void PrintGreeting() { "ballistics library. Follow the prompts to enter data.\n\n"; } +// Returns true if the program is being run in an interactive terminal. +bool IsInteractive() { +#ifdef _WIN32 + return _isatty(_fileno(stdin)) != 0; +#else + return isatty(STDIN_FILENO) != 0; +#endif +} + lob::DragFunctionT ConvertDF(double input) { switch (static_cast(std::round(input))) { case 2: // NOLINT(cppcoreguidelines-avoid-magic-numbers, @@ -509,13 +525,17 @@ void PrintExtraInfo(const lob::Input& input) { const auto kSFw = static_cast(kSF.length() + kExtra); const std::string kSS("Speed of Sound"); const auto kSSw = static_cast(kSS.length() + kExtra); + const std::string kE("Error"); + const auto kEw = static_cast(kE.length() + kExtra); std::cout << colors::kYellow << std::left << std::setw(kZAw) << kZA << std::setw(kSFw) << kSF << std::setw(kSSw) << kSS - << colors::kReset << "\n"; + << std::setw(kEw) << kE << colors::kReset << "\n"; std::cout << std::left << std::setw(kZAw) << std::fixed << std::setprecision(2) << input.zero_angle << std::setw(kSFw) << input.stability_factor << std::setw(kSSw) << input.speed_of_sound + << std::setw(kEw) << std::hex << std::showbase + << static_cast(input.error) << std::dec << std::noshowbase << "\n\n"; } @@ -618,22 +638,31 @@ int main(int argc, char* argv[]) { } if (json.empty()) { - try { - std::cin >> json; - } catch (const nlohmann::json::parse_error& e) { - std::cerr << colors::kRed - << "Error parsing JSON from stdin: " << colors::kReset - << e.what() << "\n"; - return 1; + if (example::IsInteractive()) { + for (const auto& pair : example::GetStateKeys()) { + json[pair.second] = "nan"; + } + example::PrintGreeting(); + example::PromptWizard(&json); + } else { + if (std::cin.peek() != std::char_traits::eof()) { + try { + std::cin >> json; + } catch (const nlohmann::json::parse_error& e) { + std::cerr << colors::kRed + << "Error parsing JSON from stdin: " << colors::kReset + << e.what() << "\n"; + return 1; + } + } } } if (json.empty()) { - for (const auto& pair : example::GetStateKeys()) { - json[pair.second] = "nan"; - } - example::PrintGreeting(); - example::PromptWizard(&json); + std::cerr << colors::kRed << "Error: No input data provided." + << colors::kReset << "\n\n"; + example::PrintHelp(); + return 1; } using example::StateType; From 9f112d5d7892ff91640ed9df0701237318ec38a3 Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:48:15 -0600 Subject: [PATCH 04/14] format --- example/lobber.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/lobber.cpp b/example/lobber.cpp index 56bc235..270de1a 100644 --- a/example/lobber.cpp +++ b/example/lobber.cpp @@ -535,8 +535,8 @@ void PrintExtraInfo(const lob::Input& input) { << std::setprecision(2) << input.zero_angle << std::setw(kSFw) << input.stability_factor << std::setw(kSSw) << input.speed_of_sound << std::setw(kEw) << std::hex << std::showbase - << static_cast(input.error) << std::dec << std::noshowbase - << "\n\n"; + << static_cast(input.error) << std::dec + << std::noshowbase << "\n\n"; } void PrintSolutionTable(const lob::Output* psolutions, size_t size) { From 1a75feb3f55e98dd3e2b35e66ee8595b127b8c64 Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:51:53 -0600 Subject: [PATCH 05/14] return after humidity error --- source/lob_builder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/lob_builder.cpp b/source/lob_builder.cpp index f249c04..9a42b64 100644 --- a/source/lob_builder.cpp +++ b/source/lob_builder.cpp @@ -425,6 +425,7 @@ void BuildEnvironment(Impl* pimpl) { if (pimpl->relative_humidity_percent < 0.0) { pimpl->build.error = ErrorT::kHumidityOOR; + return; } const auto kWaterVaporSaturationPressureInHg = From 645df7c144af10adb201ad760182b80084c56415 Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:38:32 -0600 Subject: [PATCH 06/14] lobber build update --- flake.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flake.nix b/flake.nix index dd87f95..fc0d0b7 100644 --- a/flake.nix +++ b/flake.nix @@ -68,6 +68,10 @@ ''; installPhase = '' mkdir -p $out/bin + if [ ! -f build/example/lobber ]; then + echo "Error: lobber binary not found at build/example/lobber" + exit 1 + fi cp build/example/lobber $out/bin/ ''; }; From f6bb6637920f6e6e2e3f72de80c976aeceb5287b Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:38:50 -0600 Subject: [PATCH 07/14] update help --- example/lobber.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/example/lobber.cpp b/example/lobber.cpp index 270de1a..23b3fff 100644 --- a/example/lobber.cpp +++ b/example/lobber.cpp @@ -49,7 +49,7 @@ void PrintVersion() { } void PrintHelp() { - std::cout << "Usage: lobber [options] stdin\n" + std::cout << "Usage: lobber [options] [< input.json]\n" << "Options:\n" << " --h, --help Show this help message\n" << " --v, --version Show version information\n" @@ -58,6 +58,9 @@ void PrintHelp() { "instead of user prompts\n" << " --of=FILE Output json file where data is saved for " "future use as an input file\n" + << "\n" + << "Note: When run interactively, a wizard prompts for input.\n" + << " When stdin is redirected, JSON data is read from stdin.\n" << "Example:\n" << colors::kYellow << " lobber --of=my_rifle_load.json\n\n" << colors::kReset; From fb9a1805d6d5acbe9dcba1fa7beb1d2abd40f6f3 Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:39:48 -0600 Subject: [PATCH 08/14] simplifies velocity error logic --- source/lob_builder.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/source/lob_builder.cpp b/source/lob_builder.cpp index 9a42b64..d40e023 100644 --- a/source/lob_builder.cpp +++ b/source/lob_builder.cpp @@ -514,7 +514,7 @@ void BuildStability(Impl* pimpl) { assert(pimpl != nullptr); assert(!std::isnan(pimpl->air_density_lbs_per_cu_ft)); - if (std::isnan(pimpl->build.velocity) || pimpl->build.velocity <= 0) { + if (pimpl->build.velocity == 0) { pimpl->build.error = ErrorT::kInitialVelocityRequired; return; } @@ -835,9 +835,6 @@ Input Builder::Build() { return pimpl_->build; } BuildOpticHeight(pimpl_); - if (pimpl_->build.error != ErrorT::kNotFormed) { - return pimpl_->build; - } BuildStability(pimpl_); if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; @@ -847,9 +844,6 @@ Input Builder::Build() { return pimpl_->build; } BuildLitzAerodynamicJump(pimpl_); - if (pimpl_->build.error != ErrorT::kNotFormed) { - return pimpl_->build; - } BuildCoriolis(pimpl_); if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; From 302da715b000466b3f5a9e57934c3efb5be4f22b Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:25:48 -0600 Subject: [PATCH 09/14] wip --- source/calc.hpp | 6 +- source/lob_builder.cpp | 95 +++++++++++++---------------- source/lob_solve.cpp | 5 +- test/source/calc_test.cpp | 29 +++++++-- test/source/lob_api_test.cpp | 6 ++ test/source/lob_builder_test.cpp | 17 +++++- test/source/lob_cwaj_test.cpp | 3 +- test/source/lob_spin_drift_test.cpp | 6 +- 8 files changed, 97 insertions(+), 70 deletions(-) diff --git a/source/calc.hpp b/source/calc.hpp index 23ff737..5bf9a4a 100644 --- a/source/calc.hpp +++ b/source/calc.hpp @@ -32,8 +32,7 @@ inline DegFT CalculateTemperatureAtAltitudeMcCoy(FeetT altitude, // https://wikipedia.org/wiki/Barometric_formula inline InHgT BarometricFormula(FeetT altitude, InHgT pressure, DegFT temperature) { - const double kGasConstant = 1716.49; // ft-lb / slug^{-1}R^{-1} - const double kMolarMassOfAir = 28.9644; // lb/lb-mol + const double kGasConstant = 1716.49; // ft-lb / slug^{-1}R^{-1} const FeetT kHeight = std::min(altitude, FeetT(kIsaTropopauseAltitudeFt)); const double kExponent = @@ -46,12 +45,9 @@ inline InHgT BarometricFormula(FeetT altitude, InHgT pressure, if (altitude > FeetT(kIsaTropopauseAltitudeFt)) { const double kNumberator = -1.0 * kStandardGravityFtPerSecSq * - kMolarMassOfAir * (altitude - kIsaTropopauseAltitudeFt).Value(); - const double kDemoninator = kGasConstant * DegRT(DegFT(kIsaMinimumTempDegF)).Value(); - output *= std::exp(kNumberator / kDemoninator); } diff --git a/source/lob_builder.cpp b/source/lob_builder.cpp index d40e023..d910f9f 100644 --- a/source/lob_builder.cpp +++ b/source/lob_builder.cpp @@ -68,15 +68,8 @@ Builder::~Builder() { pimpl_->~Impl(); } Builder::Builder(const Builder& other) : pimpl_(new(buffer_.data()) Impl(*other.pimpl_)) {} -Builder::Builder(Builder&& other) noexcept { - if (this != &other) { - if (pimpl_ != nullptr) { - pimpl_->~Impl(); - } - pimpl_ = new (buffer_.data()) Impl(); - std::swap(pimpl_, other.pimpl_); - } -} +Builder::Builder(Builder&& other) noexcept + : pimpl_(new(buffer_.data()) Impl(std::move(*other.pimpl_))) {} Builder& Builder::operator=(const Builder& rhs) { if (this != &rhs) { @@ -93,8 +86,7 @@ Builder& Builder::operator=(Builder&& rhs) noexcept { if (pimpl_ != nullptr) { pimpl_->~Impl(); } - pimpl_ = new (buffer_.data()) Impl(); - std::swap(pimpl_, rhs.pimpl_); + pimpl_ = new (buffer_.data()) Impl(std::move(*rhs.pimpl_)); } return *this; } @@ -141,10 +133,6 @@ Builder& Builder::BCAtmosphere(AtmosphereReferenceT type) { Builder& Builder::BCDragFunction(DragFunctionT type) { switch (type) { - case DragFunctionT::kG1: { - pimpl_->pdrag_lut = &kG1Drags; - break; - } case DragFunctionT::kG2: { pimpl_->pdrag_lut = &kG2Drags; break; @@ -165,7 +153,9 @@ Builder& Builder::BCDragFunction(DragFunctionT type) { pimpl_->pdrag_lut = &kG8Drags; break; } + case DragFunctionT::kG1: default: { + pimpl_->pdrag_lut = &kG1Drags; break; } } @@ -549,6 +539,40 @@ void BuildStability(Impl* pimpl) { FpsT(pimpl->build.velocity)); } +void BuildCoriolis(Impl* pimpl) { + assert(pimpl != nullptr); + + if (!std::isnan(pimpl->azimuth_rad) && !std::isnan(pimpl->latitude_rad)) { + const DegreesT kAzimuthLimit(kDegreesPerTurn); + if (pimpl->azimuth_rad > kAzimuthLimit || + pimpl->azimuth_rad < kAzimuthLimit * -1) { + pimpl->build.error = ErrorT::kAzimuthOOR; + return; + } + const DegreesT kLatitudeLimit(90); + if (pimpl->latitude_rad > kLatitudeLimit || + pimpl->latitude_rad < kLatitudeLimit * -1) { + pimpl->build.error = ErrorT::kLatitudeOOR; + return; + } + // Coriolis Effect Page 178 of Modern Exterior Ballistics - McCoy + const double kCosL = std::cos(pimpl->latitude_rad).Value(); + const double kSinA = std::sin(pimpl->azimuth_rad).Value(); + const double kSinL = std::sin(pimpl->latitude_rad).Value(); + const double kCosA = std::cos(pimpl->azimuth_rad).Value(); + + pimpl->build.corilolis.cos_l_sin_a = + 2 * kAngularVelocityOfEarthRadPerSec * kCosL * kSinA; + pimpl->build.corilolis.sin_l = 2 * kAngularVelocityOfEarthRadPerSec * kSinL; + pimpl->build.corilolis.cos_l_cos_a = + 2 * kAngularVelocityOfEarthRadPerSec * kCosL * kCosA; + } else { + pimpl->build.corilolis.cos_l_sin_a = 0; + pimpl->build.corilolis.sin_l = 0; + pimpl->build.corilolis.cos_l_cos_a = 0; + } +} + void BuildBoatright(Impl* pimpl) { assert(pimpl != nullptr); assert(pimpl->pdrag_lut != nullptr); @@ -643,7 +667,6 @@ void BuildBoatright(Impl* pimpl) { SecT t(0.0); static const FpsT kTransonicBarrier(MachT(1.2), kSos); - while (s.V().X() > kTransonicBarrier) { assert(t < SecT(60) && "This is taking too long"); SolveStep(&s, &t, pimpl->build); @@ -701,40 +724,6 @@ void BuildLitzAerodynamicJump(Impl* pimpl) { } } -void BuildCoriolis(Impl* pimpl) { - assert(pimpl != nullptr); - - if (!std::isnan(pimpl->azimuth_rad) && !std::isnan(pimpl->latitude_rad)) { - const DegreesT kAzimuthLimit(kDegreesPerTurn); - if (pimpl->azimuth_rad > kAzimuthLimit || - pimpl->azimuth_rad < kAzimuthLimit * -1) { - pimpl->build.error = ErrorT::kAzimuthOOR; - return; - } - const DegreesT kLatitudeLimit(90); - if (pimpl->latitude_rad > kLatitudeLimit || - pimpl->latitude_rad < kLatitudeLimit * -1) { - pimpl->build.error = ErrorT::kLatitudeOOR; - return; - } - // Coriolis Effect Page 178 of Modern Exterior Ballistics - McCoy - const double kCosL = std::cos(pimpl->latitude_rad).Value(); - const double kSinA = std::sin(pimpl->azimuth_rad).Value(); - const double kSinL = std::sin(pimpl->latitude_rad).Value(); - const double kCosA = std::cos(pimpl->azimuth_rad).Value(); - - pimpl->build.corilolis.cos_l_sin_a = - 2 * kAngularVelocityOfEarthRadPerSec * kCosL * kSinA; - pimpl->build.corilolis.sin_l = 2 * kAngularVelocityOfEarthRadPerSec * kSinL; - pimpl->build.corilolis.cos_l_cos_a = - 2 * kAngularVelocityOfEarthRadPerSec * kCosL * kCosA; - } else { - pimpl->build.corilolis.cos_l_sin_a = 0; - pimpl->build.corilolis.sin_l = 0; - pimpl->build.corilolis.cos_l_cos_a = 0; - } -} - void BuildZeroAngle(Impl* pimpl) { assert(pimpl != nullptr); @@ -839,15 +828,15 @@ Input Builder::Build() { if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; } - BuildBoatright(pimpl_); + BuildCoriolis(pimpl_); if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; } - BuildLitzAerodynamicJump(pimpl_); - BuildCoriolis(pimpl_); + BuildBoatright(pimpl_); if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; } + BuildLitzAerodynamicJump(pimpl_); BuildZeroAngle(pimpl_); if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; diff --git a/source/lob_solve.cpp b/source/lob_solve.cpp index 858b9f2..47820e7 100644 --- a/source/lob_solve.cpp +++ b/source/lob_solve.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "calc.hpp" #include "cartesian.hpp" @@ -44,7 +45,7 @@ Output LerpOutput(const TrajectoryStateT& s_now, const SecT t_now, void ApplyGyroscopicSpinDrift(const Input& in, Output* pouts, size_t size) { assert(pouts != nullptr); // If we can apply Boatright-Ruiz spin drift, prefer it - if (in.spindrift_factor > 0) { + if (!std::isnan(in.spindrift_factor)) { for (size_t i = 0; i < size; i++) { pouts[i].deflection += in.spindrift_factor * std::fabs(pouts[i].elevation); @@ -52,7 +53,7 @@ void ApplyGyroscopicSpinDrift(const Input& in, Output* pouts, size_t size) { return; } // If we can apply Litz spin drift, use that - if (std::fabs(in.stability_factor) > 0) { + if (std::fabs(in.stability_factor) > 0.0) { for (size_t i = 0; i < size; i++) { pouts[i].deflection += litz::CalculateGyroscopicSpinDrift(in.stability_factor, diff --git a/test/source/calc_test.cpp b/test/source/calc_test.cpp index 3f3a30b..32d68a5 100644 --- a/test/source/calc_test.cpp +++ b/test/source/calc_test.cpp @@ -17,7 +17,7 @@ namespace tests { TEST(CalcTests, CalculateTemperatureAtAltitude) { // Test data from page 167 of Modern Exterior Ballistics - McCoy - const std::vector kAltitudesFt = { + const std::vector kAltitudesFt = { 0, 500, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 15000, 20000, 25000, 30000, 35000}; const std::vector kExpectedResultsDegF = { @@ -37,7 +37,7 @@ TEST(CalcTests, CalculateTemperatureAtAltitude) { TEST(CalcTests, CalculateTemperatureAtAltitudeMcCoy) { // Test data from page 167 of Modern Exterior Ballistics - McCoy - const std::vector kAltitudesFt = { + const std::vector kAltitudesFt = { 0, 500, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 15000, 20000, 25000, 30000, 35000}; const std::vector kExpectedResultsDegF = { @@ -59,7 +59,7 @@ TEST(CalcTests, CalculateTemperatureAtAltitudeMcCoy) { TEST(CalcTests, BarometricFormula) { // Test data from page 167 of Modern Exterior Ballistics - McCoy - const std::vector kAltitudesFt = { + const std::vector kAltitudesFt = { 0, 500, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 15000, 20000, 25000, 30000, 35000}; const std::vector kExpectedResultsInHg = { @@ -77,6 +77,27 @@ TEST(CalcTests, BarometricFormula) { } } +TEST(CalcTests, BarometricFormulaStratosphere) { + // test data from + // https://www.sensorsone.com/altitude-pressure-units-conversion/ + const std::vector kAltitudesFt = {35000, 40000, 45000, 50000, 55000, + 60000, 65000, 70000, 75000, 80000, + 85000, 90000, 95000, 100000}; + const std::vector kExpectedResultsInHg = { + 7.04, 5.54, 4.36, 3.42, 2.69, 2.12, 1.67, + 1.31, 1.03, 0.82, 0.64, 0.51, 0.41, 0.32}; + const double kError = 0.025; + for (uint32_t i = 0; i < kAltitudesFt.size(); i++) { + EXPECT_NEAR( + kExpectedResultsInHg.at(i), + lob::BarometricFormula(lob::FeetT(kAltitudesFt.at(i)), + lob::InHgT(lob::kIsaSeaLevelPressureInHg), + lob::DegFT(lob::kIsaSeaLevelDegF)) + .Value(), + kError); + } +} + TEST(CalcTests, BarometricFormulaNegative) { constexpr int16_t kAltitude = -1000; constexpr double kExpectedResult = 31.02; @@ -90,7 +111,7 @@ TEST(CalcTests, BarometricFormulaNegative) { TEST(CalcTests, CalculateAirDensityAtAltitude) { // Test data from page 167 of Modern Exterior Ballistics - McCoy - const std::vector kAltitudesFt = { + const std::vector kAltitudesFt = { 0, 500, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 15000, 20000, 25000, 30000, 35000}; const double kP0 = lob::kIsaSeaLevelAirDensityLbsPerCuFt; diff --git a/test/source/lob_api_test.cpp b/test/source/lob_api_test.cpp index 30ddaac..6f54e19 100644 --- a/test/source/lob_api_test.cpp +++ b/test/source/lob_api_test.cpp @@ -191,6 +191,12 @@ TEST(LobAPITest, InchToDeg) { EXPECT_DOUBLE_EQ(kA.Value(), lob::IphyT(kB).Value()); } +TEST(LobAPITest, InchToDegZero) { + const auto kA = lob::IphyT(5); + const auto kB = lob::DegreesT(lob::InchToDeg(kA.Value(), 0.0)); + EXPECT_DOUBLE_EQ(0.0, lob::IphyT(kB).Value()); +} + TEST(LobAPITest, JToFtLbs) { const auto kA = lob::JouleT(100); const auto kB = lob::FtLbsT(lob::JToFtLbs(kA.Value())); diff --git a/test/source/lob_builder_test.cpp b/test/source/lob_builder_test.cpp index 29fdc12..c66b7a0 100644 --- a/test/source/lob_builder_test.cpp +++ b/test/source/lob_builder_test.cpp @@ -75,7 +75,6 @@ TEST_F(BuilderTestFixture, MoveConstructor) { EXPECT_EQ(kVal.velocity, kTestMuzzleVelocity); } -// Test for copy assignment operator TEST_F(BuilderTestFixture, CopyAssignmentOperator) { const double kTestBC = 0.425; const double kTestDiameter = 0.308; @@ -95,7 +94,6 @@ TEST_F(BuilderTestFixture, CopyAssignmentOperator) { EXPECT_DOUBLE_EQ(kVal2.velocity, kTestMuzzleVelocity); } -// Test for move assignment operator TEST_F(BuilderTestFixture, MoveAssignmentOperator) { const double kTestBC = 0.425; const double kTestDiameter = 0.308; @@ -156,6 +154,21 @@ TEST_F(BuilderTestFixture, BuildMissingZeroInput) { EXPECT_EQ(kResult.error, lob::ErrorT::kZeroDataRequired); } +TEST_F(BuilderTestFixture, InvalidDragFunctionIsG1) { + const double kTestBC = 1.0; + const uint16_t kTestMuzzleVelocity = 2500U; + const double kTestZeroAngle = 5.59; + const auto kInvalidDragFunction = static_cast(0xFF); + const lob::Input kResult = puut->BallisticCoefficientPsi(kTestBC) + .BCDragFunction(kInvalidDragFunction) + .InitialVelocityFps(kTestMuzzleVelocity) + .ZeroAngleMOA(kTestZeroAngle) + .Build(); + for (size_t i = 0; i < lob::kG1Drags.size(); i++) { + EXPECT_EQ(kResult.drags.at(i), lob::kG1Drags.at(i)); + } +} + TEST_F(BuilderTestFixture, BuildG1UsingCustomTable) { const double kTestBC = 1.0; const uint16_t kTestMuzzleVelocity = 2500U; diff --git a/test/source/lob_cwaj_test.cpp b/test/source/lob_cwaj_test.cpp index 569133d..bb32136 100644 --- a/test/source/lob_cwaj_test.cpp +++ b/test/source/lob_cwaj_test.cpp @@ -388,7 +388,7 @@ TEST_F(LobCWAJTestFixture, LitzLeftHandSpinRightwardWind) { kTimeOfFlightError); } } - +/* // NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_F(LobCWAJTestFixture, BoatrightRightHandSpinLeftwardWind) { ASSERT_NE(puut, nullptr); @@ -662,6 +662,7 @@ TEST_F(LobCWAJTestFixture, BoatrightLeftHandSpinRightwardWind) { kTimeOfFlightError); } } +*/ struct Shot { double diameter; diff --git a/test/source/lob_spin_drift_test.cpp b/test/source/lob_spin_drift_test.cpp index 0d7cbe0..e6460c3 100644 --- a/test/source/lob_spin_drift_test.cpp +++ b/test/source/lob_spin_drift_test.cpp @@ -423,14 +423,14 @@ TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDrift) { lob::InchToMoa(solutions.at(i).deflection, solutions.at(i).range); const double kExpectedDeflectionMoa = lob::InchToMoa(kExpected.at(i).deflection, kExpected.at(i).range); - EXPECT_NEAR(kSolutionDeflectionMoa, kExpectedDeflectionMoa, kMoaError); + //EXPECT_NEAR(kSolutionDeflectionMoa, kExpectedDeflectionMoa, kMoaError); EXPECT_NEAR(solutions.at(i).deflection, kExpected.at(i).deflection, kInchError); EXPECT_NEAR(solutions.at(i).time_of_flight, kExpected.at(i).time_of_flight, kTimeOfFlightError); } } - +/* // NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDriftFast) { ASSERT_NE(puut, nullptr); @@ -621,7 +621,7 @@ TEST_F(LobSpinTestFixture, BoatrightLeftHandSpinDriftFast) { EXPECT_NEAR(solutions.at(i).time_of_flight, kExpected.at(i).time_of_flight, kTimeOfFlightError); } -} +}*/ } // namespace tests From 775c22b34a19bb084a055d871317149ab3f5bb2b Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Sun, 28 Dec 2025 21:14:25 -0600 Subject: [PATCH 10/14] update test data for boatright spindrift --- test/source/lob_cwaj_test.cpp | 91 ++++++++++++----------- test/source/lob_spin_drift_test.cpp | 110 ++++++++++++++-------------- 2 files changed, 100 insertions(+), 101 deletions(-) diff --git a/test/source/lob_cwaj_test.cpp b/test/source/lob_cwaj_test.cpp index bb32136..8fca454 100644 --- a/test/source/lob_cwaj_test.cpp +++ b/test/source/lob_cwaj_test.cpp @@ -388,7 +388,7 @@ TEST_F(LobCWAJTestFixture, LitzLeftHandSpinRightwardWind) { kTimeOfFlightError); } } -/* + // NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_F(LobCWAJTestFixture, BoatrightRightHandSpinLeftwardWind) { ASSERT_NE(puut, nullptr); @@ -418,17 +418,17 @@ TEST_F(LobCWAJTestFixture, BoatrightRightHandSpinLeftwardWind) { {0, 3071, 5230, -2.00, 0.00, 0.000}, {150, 2992, 4963, 1.48, -0.15, 0.049}, {300, 2913, 4706, 3.99, -0.63, 0.100}, - {600, 2759, 4221, 5.89, -2.60, 0.206}, - {900, 2609, 3775, 3.21, -6.05, 0.318}, - {1200, 2464, 3366, -4.58, -11.09, 0.436}, - {1500, 2323, 2992, -18.10, -17.87, 0.562}, - {1800, 2187, 2652, -38.07, -26.52, 0.695}, - {2100, 2056, 2344, -65.32, -37.21, 0.836}, - {2400, 1929, 2064, -100.82, -50.11, 0.987}, - {2700, 1806, 1810, -145.65, -65.44, 1.148}, - {3000, 1687, 1579, -201.15, -83.43, 1.319}, - {4500, 1150, 734, -705.94, -225.13, 2.397}, - {6000, 949, 500, -1860.74, -455.01, 3.867}}; + {600, 2759, 4221, 5.89, -2.71, 0.206}, + {900, 2609, 3775, 3.21, -6.50, 0.318}, + {1200, 2464, 3366, -4.58, -11.92, 0.436}, + {1500, 2323, 2992, -18.10, -18.95, 0.562}, + {1800, 2187, 2652, -38.07, -27.89, 0.695}, + {2100, 2056, 2344, -65.32, -38.87, 0.836}, + {2400, 1929, 2064, -100.82, -52.10, 0.987}, + {2700, 1806, 1810, -145.65, -67.79, 1.148}, + {3000, 1687, 1579, -201.15, -86.20, 1.319}, + {4500, 1150, 734, -705.94, -231.13, 2.397}, + {6000, 949, 500, -1860.74, -466.01, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -487,17 +487,17 @@ TEST_F(LobCWAJTestFixture, BoatrightLeftHandSpinLeftwardWind) { {0, 3071, 5230, -2.00, 0.00, 0.000}, {150, 2992, 4963, 0.42, -0.19, 0.049}, {300, 2913, 4706, 1.87, -0.75, 0.100}, - {600, 2759, 4221, 1.63, -3.07, 0.206}, - {900, 2609, 3775, -3.18, -7.09, 0.318}, - {1200, 2464, 3366, -13.09, -12.94, 0.436}, - {1500, 2323, 2992, -28.74, -20.81, 0.562}, - {1800, 2187, 2652, -50.84, -30.86, 0.695}, - {2100, 2056, 2344, -80.21, -43.30, 0.836}, - {2400, 1929, 2064, -117.84, -58.36, 0.987}, - {2700, 1806, 1810, -164.80, -76.31, 1.148}, - {3000, 1687, 1579, -222.42, -97.46, 1.319}, - {4500, 1150, 734, -737.85, -266.99, 2.397}, - {6000, 949, 500, -1903.39, -555.46, 3.868}}; + {600, 2759, 4221, 1.63, -2.87, 0.206}, + {900, 2609, 3775, -3.18, -6.63, 0.318}, + {1200, 2464, 3366, -13.09, -12.29, 0.436}, + {1500, 2323, 2992, -28.74, -19.94, 0.562}, + {1800, 2187, 2652, -50.84, -29.76, 0.695}, + {2100, 2056, 2344, -80.21, -41.94, 0.836}, + {2400, 1929, 2064, -117.84, -56.71, 0.987}, + {2700, 1806, 1810, -164.80, -74.34, 1.148}, + {3000, 1687, 1579, -222.42, -95.14, 1.319}, + {4500, 1150, 734, -737.85, -261.60, 2.397}, + {6000, 949, 500, -1903.39, -545.46, 3.868}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -555,17 +555,17 @@ TEST_F(LobCWAJTestFixture, BoatrightRightHandSpinRightwardWind) { {0, 3071, 5230, -2.00, 0.00, 0.000}, {150, 2992, 4963, 0.42, 0.19, 0.049}, {300, 2913, 4706, 1.87, 0.75, 0.100}, - {600, 2759, 4221, 1.63, 3.07, 0.206}, - {900, 2609, 3775, -3.18, 7.09, 0.318}, - {1200, 2464, 3366, -13.09, 12.94, 0.436}, - {1500, 2323, 2992, -28.74, 20.81, 0.562}, - {1800, 2187, 2652, -50.84, 30.86, 0.695}, - {2100, 2056, 2344, -80.21, 43.30, 0.836}, - {2400, 1929, 2064, -117.84, 58.36, 0.987}, - {2700, 1806, 1810, -164.80, 76.31, 1.148}, - {3000, 1687, 1579, -222.42, 97.46, 1.319}, - {4500, 1150, 734, -737.85, 266.99, 2.397}, - {6000, 949, 500, -1903.39, 555.46, 3.868}}; + {600, 2759, 4221, 1.63, 2.87, 0.206}, + {900, 2609, 3775, -3.18, 6.63, 0.318}, + {1200, 2464, 3366, -13.09, 12.29, 0.436}, + {1500, 2323, 2992, -28.74, 19.94, 0.562}, + {1800, 2187, 2652, -50.84, 29.76, 0.695}, + {2100, 2056, 2344, -80.21, 41.94, 0.836}, + {2400, 1929, 2064, -117.84, 56.71, 0.987}, + {2700, 1806, 1810, -164.80, 74.34, 1.148}, + {3000, 1687, 1579, -222.42, 95.14, 1.319}, + {4500, 1150, 734, -737.85, 261.60, 2.397}, + {6000, 949, 500, -1903.39, 545.68, 3.868}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -624,17 +624,17 @@ TEST_F(LobCWAJTestFixture, BoatrightLeftHandSpinRightwardWind) { {0, 3071, 5230, -2.00, 0.00, 0.000}, {150, 2992, 4963, 1.48, 0.15, 0.049}, {300, 2913, 4706, 3.99, 0.63, 0.100}, - {600, 2759, 4221, 5.89, 2.60, 0.206}, - {900, 2609, 3775, 3.21, 6.05, 0.318}, - {1200, 2464, 3366, -4.58, 11.09, 0.436}, - {1500, 2323, 2992, -18.10, 17.87, 0.562}, - {1800, 2187, 2652, -38.07, 26.52, 0.695}, - {2100, 2056, 2344, -65.32, 37.21, 0.836}, - {2400, 1929, 2064, -100.82, 50.11, 0.987}, - {2700, 1806, 1810, -145.65, 65.44, 1.148}, - {3000, 1687, 1579, -201.15, 83.43, 1.319}, - {4500, 1150, 734, -705.94, 225.13, 2.397}, - {6000, 949, 500, -1860.74, 455.01, 3.867}}; + {600, 2759, 4221, 5.89, 2.71, 0.206}, + {900, 2609, 3775, 3.21, 6.50, 0.318}, + {1200, 2464, 3366, -4.58, 11.92, 0.436}, + {1500, 2323, 2992, -18.10, 18.95, 0.562}, + {1800, 2187, 2652, -38.07, 27.88, 0.695}, + {2100, 2056, 2344, -65.32, 38.87, 0.836}, + {2400, 1929, 2064, -100.82, 52.10, 0.987}, + {2700, 1806, 1810, -145.65, 67.79, 1.148}, + {3000, 1687, 1579, -201.15, 86.20, 1.319}, + {4500, 1150, 734, -705.94, 231.13, 2.397}, + {6000, 949, 500, -1860.74, 466.23, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -662,7 +662,6 @@ TEST_F(LobCWAJTestFixture, BoatrightLeftHandSpinRightwardWind) { kTimeOfFlightError); } } -*/ struct Shot { double diameter; diff --git a/test/source/lob_spin_drift_test.cpp b/test/source/lob_spin_drift_test.cpp index e6460c3..d6b6c44 100644 --- a/test/source/lob_spin_drift_test.cpp +++ b/test/source/lob_spin_drift_test.cpp @@ -390,19 +390,19 @@ TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDrift) { 1800, 2100, 2400, 2700, 3000, 4500, 6000}; const std::vector kExpected = { {0, 3071, 5230, -2.00, 0.00, 0.000}, - {150, 2992, 4963, 0.95, 0.02, 0.049}, - {300, 2913, 4706, 2.93, 0.06, 0.100}, - {600, 2759, 4221, 3.76, 0.23, 0.206}, - {900, 2609, 3775, 0.02, 0.52, 0.318}, - {1200, 2464, 3366, -8.84, 0.93, 0.436}, - {1500, 2323, 2992, -23.42, 1.47, 0.562}, - {1800, 2187, 2652, -44.45, 2.17, 0.695}, - {2100, 2056, 2344, -72.77, 3.05, 0.836}, - {2400, 1929, 2064, -109.33, 4.12, 0.987}, - {2700, 1806, 1810, -155.23, 5.44, 1.148}, - {3000, 1687, 1579, -211.78, 7.02, 1.319}, - {4500, 1150, 734, -721.84, 20.93, 2.397}, - {6000, 949, 500, -1882.01, 50.22, 3.867}}; + {150, 2992, 4963, 0.95, 0.00, 0.049}, + {300, 2913, 4706, 2.93, 0.00, 0.100}, + {600, 2759, 4221, 3.76, 0.00, 0.206}, + {900, 2609, 3775, 0.02, 0.00, 0.318}, + {1200, 2464, 3366, -8.84, 0.19, 0.436}, + {1500, 2323, 2992, -23.42, 0.49, 0.562}, + {1800, 2187, 2652, -44.45, 0.94, 0.695}, + {2100, 2056, 2344, -72.77, 1.54, 0.836}, + {2400, 1929, 2064, -109.33, 2.31, 0.987}, + {2700, 1806, 1810, -155.23, 3.27, 1.148}, + {3000, 1687, 1579, -211.78, 4.47, 1.319}, + {4500, 1150, 734, -721.84, 15.23, 2.397}, + {6000, 949, 500, -1882.01, 39.72, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -423,14 +423,14 @@ TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDrift) { lob::InchToMoa(solutions.at(i).deflection, solutions.at(i).range); const double kExpectedDeflectionMoa = lob::InchToMoa(kExpected.at(i).deflection, kExpected.at(i).range); - //EXPECT_NEAR(kSolutionDeflectionMoa, kExpectedDeflectionMoa, kMoaError); + EXPECT_NEAR(kSolutionDeflectionMoa, kExpectedDeflectionMoa, kMoaError); EXPECT_NEAR(solutions.at(i).deflection, kExpected.at(i).deflection, kInchError); EXPECT_NEAR(solutions.at(i).time_of_flight, kExpected.at(i).time_of_flight, kTimeOfFlightError); } } -/* + // NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDriftFast) { ASSERT_NE(puut, nullptr); @@ -454,19 +454,19 @@ TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDriftFast) { 1800, 2100, 2400, 2700, 3000, 4500, 6000}; const std::vector kExpected = { {0, 3071, 5230, -2.00, 0.00, 0.000}, - {150, 2992, 4963, 0.95, 0.02, 0.049}, - {300, 2913, 4706, 2.93, 0.08, 0.100}, - {600, 2759, 4221, 3.76, 0.29, 0.206}, - {900, 2609, 3775, 0.02, 0.64, 0.318}, - {1200, 2464, 3366, -8.84, 1.15, 0.436}, - {1500, 2323, 2992, -23.42, 1.83, 0.562}, - {1800, 2187, 2652, -44.45, 2.70, 0.695}, - {2100, 2056, 2344, -72.77, 3.79, 0.836}, - {2400, 1929, 2064, -109.33, 5.13, 0.987}, - {2700, 1806, 1810, -155.23, 6.76, 1.148}, - {3000, 1687, 1579, -211.78, 8.72, 1.319}, - {4500, 1150, 734, -721.84, 26.01, 2.397}, - {6000, 949, 500, -1882.01, 62.42, 3.867}}; + {150, 2992, 4963, 0.95, 0.00, 0.049}, + {300, 2913, 4706, 2.93, 0.00, 0.100}, + {600, 2759, 4221, 3.76, 0.00, 0.206}, + {900, 2609, 3775, 0.02, 0.00, 0.318}, + {1200, 2464, 3366, -8.84, 0.23, 0.436}, + {1500, 2323, 2992, -23.42, 0.607, 0.562}, + {1800, 2187, 2652, -44.45, 1.15, 0.695}, + {2100, 2056, 2344, -72.77, 1.89, 0.836}, + {2400, 1929, 2064, -109.33, 2.83, 0.987}, + {2700, 1806, 1810, -155.23, 4.02, 1.148}, + {3000, 1687, 1579, -211.78, 5.49, 1.319}, + {4500, 1150, 734, -721.84, 18.70, 2.397}, + {6000, 949, 500, -1882.01, 48.77, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -518,19 +518,19 @@ TEST_F(LobSpinTestFixture, BoatrightLeftHandSpinDrift) { 1800, 2100, 2400, 2700, 3000, 4500, 6000}; const std::vector kExpected = { {0, 3071, 5230, -2.00, 0.00, 0.000}, - {150, 2992, 4963, 0.95, -0.02, 0.049}, - {300, 2913, 4706, 2.93, -0.06, 0.100}, - {600, 2759, 4221, 3.76, -0.23, 0.206}, - {900, 2609, 3775, 0.02, -0.52, 0.318}, - {1200, 2464, 3366, -8.84, -0.93, 0.436}, - {1500, 2323, 2992, -23.42, -1.47, 0.562}, - {1800, 2187, 2652, -44.45, -2.17, 0.695}, - {2100, 2056, 2344, -72.77, -3.05, 0.836}, - {2400, 1929, 2064, -109.33, -4.12, 0.987}, - {2700, 1806, 1810, -155.23, -5.44, 1.148}, - {3000, 1687, 1579, -211.78, -7.02, 1.319}, - {4500, 1150, 734, -721.84, -20.93, 2.397}, - {6000, 949, 500, -1882.01, -50.22, 3.867}}; + {150, 2992, 4963, 0.95, -0.00, 0.049}, + {300, 2913, 4706, 2.93, -0.00, 0.100}, + {600, 2759, 4221, 3.76, -0.00, 0.206}, + {900, 2609, 3775, 0.02, -0.00, 0.318}, + {1200, 2464, 3366, -8.84, -0.19, 0.436}, + {1500, 2323, 2992, -23.42, -0.49, 0.562}, + {1800, 2187, 2652, -44.45, -0.94, 0.695}, + {2100, 2056, 2344, -72.77, -1.54, 0.836}, + {2400, 1929, 2064, -109.33, -2.31, 0.987}, + {2700, 1806, 1810, -155.23, -3.27, 1.148}, + {3000, 1687, 1579, -211.78, -4.47, 1.319}, + {4500, 1150, 734, -721.84, -15.23, 2.397}, + {6000, 949, 500, -1882.01, -39.72, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -582,19 +582,19 @@ TEST_F(LobSpinTestFixture, BoatrightLeftHandSpinDriftFast) { 1800, 2100, 2400, 2700, 3000, 4500, 6000}; const std::vector kExpected = { {0, 3071, 5230, -2.00, 0.00, 0.000}, - {150, 2992, 4963, 0.95, -0.02, 0.049}, - {300, 2913, 4706, 2.93, -0.08, 0.100}, - {600, 2759, 4221, 3.76, -0.29, 0.206}, - {900, 2609, 3775, 0.02, -0.64, 0.318}, - {1200, 2464, 3366, -8.84, -1.15, 0.436}, - {1500, 2323, 2992, -23.42, -1.83, 0.562}, - {1800, 2187, 2652, -44.45, -2.70, 0.695}, - {2100, 2056, 2344, -72.77, -3.79, 0.836}, - {2400, 1929, 2064, -109.33, -5.13, 0.987}, - {2700, 1806, 1810, -155.23, -6.76, 1.148}, - {3000, 1687, 1579, -211.78, -8.72, 1.319}, - {4500, 1150, 734, -721.84, -26.01, 2.397}, - {6000, 949, 500, -1882.01, -62.42, 3.867}}; + {150, 2992, 4963, 0.95, -0.00, 0.049}, + {300, 2913, 4706, 2.93, -0.00, 0.100}, + {600, 2759, 4221, 3.76, -0.00, 0.206}, + {900, 2609, 3775, 0.02, -0.00, 0.318}, + {1200, 2464, 3366, -8.84, -0.23, 0.436}, + {1500, 2323, 2992, -23.42, -0.607, 0.562}, + {1800, 2187, 2652, -44.45, -1.15, 0.695}, + {2100, 2056, 2344, -72.77, -1.89, 0.836}, + {2400, 1929, 2064, -109.33, -2.83, 0.987}, + {2700, 1806, 1810, -155.23, -4.02, 1.148}, + {3000, 1687, 1579, -211.78, -5.49, 1.319}, + {4500, 1150, 734, -721.84, -18.70, 2.397}, + {6000, 949, 500, -1882.01, -48.77, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -621,7 +621,7 @@ TEST_F(LobSpinTestFixture, BoatrightLeftHandSpinDriftFast) { EXPECT_NEAR(solutions.at(i).time_of_flight, kExpected.at(i).time_of_flight, kTimeOfFlightError); } -}*/ +} } // namespace tests From f404bf9991a36741df9f1cd8c0c352588927c560 Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:25:48 -0600 Subject: [PATCH 11/14] wip --- source/calc.hpp | 6 +- source/lob_builder.cpp | 95 +++++++++++++---------------- source/lob_solve.cpp | 5 +- test/source/calc_test.cpp | 29 +++++++-- test/source/lob_api_test.cpp | 6 ++ test/source/lob_builder_test.cpp | 17 +++++- test/source/lob_cwaj_test.cpp | 3 +- test/source/lob_spin_drift_test.cpp | 6 +- 8 files changed, 97 insertions(+), 70 deletions(-) diff --git a/source/calc.hpp b/source/calc.hpp index 23ff737..5bf9a4a 100644 --- a/source/calc.hpp +++ b/source/calc.hpp @@ -32,8 +32,7 @@ inline DegFT CalculateTemperatureAtAltitudeMcCoy(FeetT altitude, // https://wikipedia.org/wiki/Barometric_formula inline InHgT BarometricFormula(FeetT altitude, InHgT pressure, DegFT temperature) { - const double kGasConstant = 1716.49; // ft-lb / slug^{-1}R^{-1} - const double kMolarMassOfAir = 28.9644; // lb/lb-mol + const double kGasConstant = 1716.49; // ft-lb / slug^{-1}R^{-1} const FeetT kHeight = std::min(altitude, FeetT(kIsaTropopauseAltitudeFt)); const double kExponent = @@ -46,12 +45,9 @@ inline InHgT BarometricFormula(FeetT altitude, InHgT pressure, if (altitude > FeetT(kIsaTropopauseAltitudeFt)) { const double kNumberator = -1.0 * kStandardGravityFtPerSecSq * - kMolarMassOfAir * (altitude - kIsaTropopauseAltitudeFt).Value(); - const double kDemoninator = kGasConstant * DegRT(DegFT(kIsaMinimumTempDegF)).Value(); - output *= std::exp(kNumberator / kDemoninator); } diff --git a/source/lob_builder.cpp b/source/lob_builder.cpp index d40e023..d910f9f 100644 --- a/source/lob_builder.cpp +++ b/source/lob_builder.cpp @@ -68,15 +68,8 @@ Builder::~Builder() { pimpl_->~Impl(); } Builder::Builder(const Builder& other) : pimpl_(new(buffer_.data()) Impl(*other.pimpl_)) {} -Builder::Builder(Builder&& other) noexcept { - if (this != &other) { - if (pimpl_ != nullptr) { - pimpl_->~Impl(); - } - pimpl_ = new (buffer_.data()) Impl(); - std::swap(pimpl_, other.pimpl_); - } -} +Builder::Builder(Builder&& other) noexcept + : pimpl_(new(buffer_.data()) Impl(std::move(*other.pimpl_))) {} Builder& Builder::operator=(const Builder& rhs) { if (this != &rhs) { @@ -93,8 +86,7 @@ Builder& Builder::operator=(Builder&& rhs) noexcept { if (pimpl_ != nullptr) { pimpl_->~Impl(); } - pimpl_ = new (buffer_.data()) Impl(); - std::swap(pimpl_, rhs.pimpl_); + pimpl_ = new (buffer_.data()) Impl(std::move(*rhs.pimpl_)); } return *this; } @@ -141,10 +133,6 @@ Builder& Builder::BCAtmosphere(AtmosphereReferenceT type) { Builder& Builder::BCDragFunction(DragFunctionT type) { switch (type) { - case DragFunctionT::kG1: { - pimpl_->pdrag_lut = &kG1Drags; - break; - } case DragFunctionT::kG2: { pimpl_->pdrag_lut = &kG2Drags; break; @@ -165,7 +153,9 @@ Builder& Builder::BCDragFunction(DragFunctionT type) { pimpl_->pdrag_lut = &kG8Drags; break; } + case DragFunctionT::kG1: default: { + pimpl_->pdrag_lut = &kG1Drags; break; } } @@ -549,6 +539,40 @@ void BuildStability(Impl* pimpl) { FpsT(pimpl->build.velocity)); } +void BuildCoriolis(Impl* pimpl) { + assert(pimpl != nullptr); + + if (!std::isnan(pimpl->azimuth_rad) && !std::isnan(pimpl->latitude_rad)) { + const DegreesT kAzimuthLimit(kDegreesPerTurn); + if (pimpl->azimuth_rad > kAzimuthLimit || + pimpl->azimuth_rad < kAzimuthLimit * -1) { + pimpl->build.error = ErrorT::kAzimuthOOR; + return; + } + const DegreesT kLatitudeLimit(90); + if (pimpl->latitude_rad > kLatitudeLimit || + pimpl->latitude_rad < kLatitudeLimit * -1) { + pimpl->build.error = ErrorT::kLatitudeOOR; + return; + } + // Coriolis Effect Page 178 of Modern Exterior Ballistics - McCoy + const double kCosL = std::cos(pimpl->latitude_rad).Value(); + const double kSinA = std::sin(pimpl->azimuth_rad).Value(); + const double kSinL = std::sin(pimpl->latitude_rad).Value(); + const double kCosA = std::cos(pimpl->azimuth_rad).Value(); + + pimpl->build.corilolis.cos_l_sin_a = + 2 * kAngularVelocityOfEarthRadPerSec * kCosL * kSinA; + pimpl->build.corilolis.sin_l = 2 * kAngularVelocityOfEarthRadPerSec * kSinL; + pimpl->build.corilolis.cos_l_cos_a = + 2 * kAngularVelocityOfEarthRadPerSec * kCosL * kCosA; + } else { + pimpl->build.corilolis.cos_l_sin_a = 0; + pimpl->build.corilolis.sin_l = 0; + pimpl->build.corilolis.cos_l_cos_a = 0; + } +} + void BuildBoatright(Impl* pimpl) { assert(pimpl != nullptr); assert(pimpl->pdrag_lut != nullptr); @@ -643,7 +667,6 @@ void BuildBoatright(Impl* pimpl) { SecT t(0.0); static const FpsT kTransonicBarrier(MachT(1.2), kSos); - while (s.V().X() > kTransonicBarrier) { assert(t < SecT(60) && "This is taking too long"); SolveStep(&s, &t, pimpl->build); @@ -701,40 +724,6 @@ void BuildLitzAerodynamicJump(Impl* pimpl) { } } -void BuildCoriolis(Impl* pimpl) { - assert(pimpl != nullptr); - - if (!std::isnan(pimpl->azimuth_rad) && !std::isnan(pimpl->latitude_rad)) { - const DegreesT kAzimuthLimit(kDegreesPerTurn); - if (pimpl->azimuth_rad > kAzimuthLimit || - pimpl->azimuth_rad < kAzimuthLimit * -1) { - pimpl->build.error = ErrorT::kAzimuthOOR; - return; - } - const DegreesT kLatitudeLimit(90); - if (pimpl->latitude_rad > kLatitudeLimit || - pimpl->latitude_rad < kLatitudeLimit * -1) { - pimpl->build.error = ErrorT::kLatitudeOOR; - return; - } - // Coriolis Effect Page 178 of Modern Exterior Ballistics - McCoy - const double kCosL = std::cos(pimpl->latitude_rad).Value(); - const double kSinA = std::sin(pimpl->azimuth_rad).Value(); - const double kSinL = std::sin(pimpl->latitude_rad).Value(); - const double kCosA = std::cos(pimpl->azimuth_rad).Value(); - - pimpl->build.corilolis.cos_l_sin_a = - 2 * kAngularVelocityOfEarthRadPerSec * kCosL * kSinA; - pimpl->build.corilolis.sin_l = 2 * kAngularVelocityOfEarthRadPerSec * kSinL; - pimpl->build.corilolis.cos_l_cos_a = - 2 * kAngularVelocityOfEarthRadPerSec * kCosL * kCosA; - } else { - pimpl->build.corilolis.cos_l_sin_a = 0; - pimpl->build.corilolis.sin_l = 0; - pimpl->build.corilolis.cos_l_cos_a = 0; - } -} - void BuildZeroAngle(Impl* pimpl) { assert(pimpl != nullptr); @@ -839,15 +828,15 @@ Input Builder::Build() { if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; } - BuildBoatright(pimpl_); + BuildCoriolis(pimpl_); if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; } - BuildLitzAerodynamicJump(pimpl_); - BuildCoriolis(pimpl_); + BuildBoatright(pimpl_); if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; } + BuildLitzAerodynamicJump(pimpl_); BuildZeroAngle(pimpl_); if (pimpl_->build.error != ErrorT::kNotFormed) { return pimpl_->build; diff --git a/source/lob_solve.cpp b/source/lob_solve.cpp index 858b9f2..47820e7 100644 --- a/source/lob_solve.cpp +++ b/source/lob_solve.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "calc.hpp" #include "cartesian.hpp" @@ -44,7 +45,7 @@ Output LerpOutput(const TrajectoryStateT& s_now, const SecT t_now, void ApplyGyroscopicSpinDrift(const Input& in, Output* pouts, size_t size) { assert(pouts != nullptr); // If we can apply Boatright-Ruiz spin drift, prefer it - if (in.spindrift_factor > 0) { + if (!std::isnan(in.spindrift_factor)) { for (size_t i = 0; i < size; i++) { pouts[i].deflection += in.spindrift_factor * std::fabs(pouts[i].elevation); @@ -52,7 +53,7 @@ void ApplyGyroscopicSpinDrift(const Input& in, Output* pouts, size_t size) { return; } // If we can apply Litz spin drift, use that - if (std::fabs(in.stability_factor) > 0) { + if (std::fabs(in.stability_factor) > 0.0) { for (size_t i = 0; i < size; i++) { pouts[i].deflection += litz::CalculateGyroscopicSpinDrift(in.stability_factor, diff --git a/test/source/calc_test.cpp b/test/source/calc_test.cpp index 3f3a30b..32d68a5 100644 --- a/test/source/calc_test.cpp +++ b/test/source/calc_test.cpp @@ -17,7 +17,7 @@ namespace tests { TEST(CalcTests, CalculateTemperatureAtAltitude) { // Test data from page 167 of Modern Exterior Ballistics - McCoy - const std::vector kAltitudesFt = { + const std::vector kAltitudesFt = { 0, 500, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 15000, 20000, 25000, 30000, 35000}; const std::vector kExpectedResultsDegF = { @@ -37,7 +37,7 @@ TEST(CalcTests, CalculateTemperatureAtAltitude) { TEST(CalcTests, CalculateTemperatureAtAltitudeMcCoy) { // Test data from page 167 of Modern Exterior Ballistics - McCoy - const std::vector kAltitudesFt = { + const std::vector kAltitudesFt = { 0, 500, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 15000, 20000, 25000, 30000, 35000}; const std::vector kExpectedResultsDegF = { @@ -59,7 +59,7 @@ TEST(CalcTests, CalculateTemperatureAtAltitudeMcCoy) { TEST(CalcTests, BarometricFormula) { // Test data from page 167 of Modern Exterior Ballistics - McCoy - const std::vector kAltitudesFt = { + const std::vector kAltitudesFt = { 0, 500, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 15000, 20000, 25000, 30000, 35000}; const std::vector kExpectedResultsInHg = { @@ -77,6 +77,27 @@ TEST(CalcTests, BarometricFormula) { } } +TEST(CalcTests, BarometricFormulaStratosphere) { + // test data from + // https://www.sensorsone.com/altitude-pressure-units-conversion/ + const std::vector kAltitudesFt = {35000, 40000, 45000, 50000, 55000, + 60000, 65000, 70000, 75000, 80000, + 85000, 90000, 95000, 100000}; + const std::vector kExpectedResultsInHg = { + 7.04, 5.54, 4.36, 3.42, 2.69, 2.12, 1.67, + 1.31, 1.03, 0.82, 0.64, 0.51, 0.41, 0.32}; + const double kError = 0.025; + for (uint32_t i = 0; i < kAltitudesFt.size(); i++) { + EXPECT_NEAR( + kExpectedResultsInHg.at(i), + lob::BarometricFormula(lob::FeetT(kAltitudesFt.at(i)), + lob::InHgT(lob::kIsaSeaLevelPressureInHg), + lob::DegFT(lob::kIsaSeaLevelDegF)) + .Value(), + kError); + } +} + TEST(CalcTests, BarometricFormulaNegative) { constexpr int16_t kAltitude = -1000; constexpr double kExpectedResult = 31.02; @@ -90,7 +111,7 @@ TEST(CalcTests, BarometricFormulaNegative) { TEST(CalcTests, CalculateAirDensityAtAltitude) { // Test data from page 167 of Modern Exterior Ballistics - McCoy - const std::vector kAltitudesFt = { + const std::vector kAltitudesFt = { 0, 500, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 15000, 20000, 25000, 30000, 35000}; const double kP0 = lob::kIsaSeaLevelAirDensityLbsPerCuFt; diff --git a/test/source/lob_api_test.cpp b/test/source/lob_api_test.cpp index 30ddaac..6f54e19 100644 --- a/test/source/lob_api_test.cpp +++ b/test/source/lob_api_test.cpp @@ -191,6 +191,12 @@ TEST(LobAPITest, InchToDeg) { EXPECT_DOUBLE_EQ(kA.Value(), lob::IphyT(kB).Value()); } +TEST(LobAPITest, InchToDegZero) { + const auto kA = lob::IphyT(5); + const auto kB = lob::DegreesT(lob::InchToDeg(kA.Value(), 0.0)); + EXPECT_DOUBLE_EQ(0.0, lob::IphyT(kB).Value()); +} + TEST(LobAPITest, JToFtLbs) { const auto kA = lob::JouleT(100); const auto kB = lob::FtLbsT(lob::JToFtLbs(kA.Value())); diff --git a/test/source/lob_builder_test.cpp b/test/source/lob_builder_test.cpp index 29fdc12..c66b7a0 100644 --- a/test/source/lob_builder_test.cpp +++ b/test/source/lob_builder_test.cpp @@ -75,7 +75,6 @@ TEST_F(BuilderTestFixture, MoveConstructor) { EXPECT_EQ(kVal.velocity, kTestMuzzleVelocity); } -// Test for copy assignment operator TEST_F(BuilderTestFixture, CopyAssignmentOperator) { const double kTestBC = 0.425; const double kTestDiameter = 0.308; @@ -95,7 +94,6 @@ TEST_F(BuilderTestFixture, CopyAssignmentOperator) { EXPECT_DOUBLE_EQ(kVal2.velocity, kTestMuzzleVelocity); } -// Test for move assignment operator TEST_F(BuilderTestFixture, MoveAssignmentOperator) { const double kTestBC = 0.425; const double kTestDiameter = 0.308; @@ -156,6 +154,21 @@ TEST_F(BuilderTestFixture, BuildMissingZeroInput) { EXPECT_EQ(kResult.error, lob::ErrorT::kZeroDataRequired); } +TEST_F(BuilderTestFixture, InvalidDragFunctionIsG1) { + const double kTestBC = 1.0; + const uint16_t kTestMuzzleVelocity = 2500U; + const double kTestZeroAngle = 5.59; + const auto kInvalidDragFunction = static_cast(0xFF); + const lob::Input kResult = puut->BallisticCoefficientPsi(kTestBC) + .BCDragFunction(kInvalidDragFunction) + .InitialVelocityFps(kTestMuzzleVelocity) + .ZeroAngleMOA(kTestZeroAngle) + .Build(); + for (size_t i = 0; i < lob::kG1Drags.size(); i++) { + EXPECT_EQ(kResult.drags.at(i), lob::kG1Drags.at(i)); + } +} + TEST_F(BuilderTestFixture, BuildG1UsingCustomTable) { const double kTestBC = 1.0; const uint16_t kTestMuzzleVelocity = 2500U; diff --git a/test/source/lob_cwaj_test.cpp b/test/source/lob_cwaj_test.cpp index 569133d..bb32136 100644 --- a/test/source/lob_cwaj_test.cpp +++ b/test/source/lob_cwaj_test.cpp @@ -388,7 +388,7 @@ TEST_F(LobCWAJTestFixture, LitzLeftHandSpinRightwardWind) { kTimeOfFlightError); } } - +/* // NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_F(LobCWAJTestFixture, BoatrightRightHandSpinLeftwardWind) { ASSERT_NE(puut, nullptr); @@ -662,6 +662,7 @@ TEST_F(LobCWAJTestFixture, BoatrightLeftHandSpinRightwardWind) { kTimeOfFlightError); } } +*/ struct Shot { double diameter; diff --git a/test/source/lob_spin_drift_test.cpp b/test/source/lob_spin_drift_test.cpp index 0d7cbe0..e6460c3 100644 --- a/test/source/lob_spin_drift_test.cpp +++ b/test/source/lob_spin_drift_test.cpp @@ -423,14 +423,14 @@ TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDrift) { lob::InchToMoa(solutions.at(i).deflection, solutions.at(i).range); const double kExpectedDeflectionMoa = lob::InchToMoa(kExpected.at(i).deflection, kExpected.at(i).range); - EXPECT_NEAR(kSolutionDeflectionMoa, kExpectedDeflectionMoa, kMoaError); + //EXPECT_NEAR(kSolutionDeflectionMoa, kExpectedDeflectionMoa, kMoaError); EXPECT_NEAR(solutions.at(i).deflection, kExpected.at(i).deflection, kInchError); EXPECT_NEAR(solutions.at(i).time_of_flight, kExpected.at(i).time_of_flight, kTimeOfFlightError); } } - +/* // NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDriftFast) { ASSERT_NE(puut, nullptr); @@ -621,7 +621,7 @@ TEST_F(LobSpinTestFixture, BoatrightLeftHandSpinDriftFast) { EXPECT_NEAR(solutions.at(i).time_of_flight, kExpected.at(i).time_of_flight, kTimeOfFlightError); } -} +}*/ } // namespace tests From 6c7470895627714729fce355eb7d23d7732b6ca7 Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Sun, 28 Dec 2025 21:14:25 -0600 Subject: [PATCH 12/14] update test data for boatright spindrift --- test/source/lob_cwaj_test.cpp | 91 ++++++++++++----------- test/source/lob_spin_drift_test.cpp | 110 ++++++++++++++-------------- 2 files changed, 100 insertions(+), 101 deletions(-) diff --git a/test/source/lob_cwaj_test.cpp b/test/source/lob_cwaj_test.cpp index bb32136..8fca454 100644 --- a/test/source/lob_cwaj_test.cpp +++ b/test/source/lob_cwaj_test.cpp @@ -388,7 +388,7 @@ TEST_F(LobCWAJTestFixture, LitzLeftHandSpinRightwardWind) { kTimeOfFlightError); } } -/* + // NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_F(LobCWAJTestFixture, BoatrightRightHandSpinLeftwardWind) { ASSERT_NE(puut, nullptr); @@ -418,17 +418,17 @@ TEST_F(LobCWAJTestFixture, BoatrightRightHandSpinLeftwardWind) { {0, 3071, 5230, -2.00, 0.00, 0.000}, {150, 2992, 4963, 1.48, -0.15, 0.049}, {300, 2913, 4706, 3.99, -0.63, 0.100}, - {600, 2759, 4221, 5.89, -2.60, 0.206}, - {900, 2609, 3775, 3.21, -6.05, 0.318}, - {1200, 2464, 3366, -4.58, -11.09, 0.436}, - {1500, 2323, 2992, -18.10, -17.87, 0.562}, - {1800, 2187, 2652, -38.07, -26.52, 0.695}, - {2100, 2056, 2344, -65.32, -37.21, 0.836}, - {2400, 1929, 2064, -100.82, -50.11, 0.987}, - {2700, 1806, 1810, -145.65, -65.44, 1.148}, - {3000, 1687, 1579, -201.15, -83.43, 1.319}, - {4500, 1150, 734, -705.94, -225.13, 2.397}, - {6000, 949, 500, -1860.74, -455.01, 3.867}}; + {600, 2759, 4221, 5.89, -2.71, 0.206}, + {900, 2609, 3775, 3.21, -6.50, 0.318}, + {1200, 2464, 3366, -4.58, -11.92, 0.436}, + {1500, 2323, 2992, -18.10, -18.95, 0.562}, + {1800, 2187, 2652, -38.07, -27.89, 0.695}, + {2100, 2056, 2344, -65.32, -38.87, 0.836}, + {2400, 1929, 2064, -100.82, -52.10, 0.987}, + {2700, 1806, 1810, -145.65, -67.79, 1.148}, + {3000, 1687, 1579, -201.15, -86.20, 1.319}, + {4500, 1150, 734, -705.94, -231.13, 2.397}, + {6000, 949, 500, -1860.74, -466.01, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -487,17 +487,17 @@ TEST_F(LobCWAJTestFixture, BoatrightLeftHandSpinLeftwardWind) { {0, 3071, 5230, -2.00, 0.00, 0.000}, {150, 2992, 4963, 0.42, -0.19, 0.049}, {300, 2913, 4706, 1.87, -0.75, 0.100}, - {600, 2759, 4221, 1.63, -3.07, 0.206}, - {900, 2609, 3775, -3.18, -7.09, 0.318}, - {1200, 2464, 3366, -13.09, -12.94, 0.436}, - {1500, 2323, 2992, -28.74, -20.81, 0.562}, - {1800, 2187, 2652, -50.84, -30.86, 0.695}, - {2100, 2056, 2344, -80.21, -43.30, 0.836}, - {2400, 1929, 2064, -117.84, -58.36, 0.987}, - {2700, 1806, 1810, -164.80, -76.31, 1.148}, - {3000, 1687, 1579, -222.42, -97.46, 1.319}, - {4500, 1150, 734, -737.85, -266.99, 2.397}, - {6000, 949, 500, -1903.39, -555.46, 3.868}}; + {600, 2759, 4221, 1.63, -2.87, 0.206}, + {900, 2609, 3775, -3.18, -6.63, 0.318}, + {1200, 2464, 3366, -13.09, -12.29, 0.436}, + {1500, 2323, 2992, -28.74, -19.94, 0.562}, + {1800, 2187, 2652, -50.84, -29.76, 0.695}, + {2100, 2056, 2344, -80.21, -41.94, 0.836}, + {2400, 1929, 2064, -117.84, -56.71, 0.987}, + {2700, 1806, 1810, -164.80, -74.34, 1.148}, + {3000, 1687, 1579, -222.42, -95.14, 1.319}, + {4500, 1150, 734, -737.85, -261.60, 2.397}, + {6000, 949, 500, -1903.39, -545.46, 3.868}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -555,17 +555,17 @@ TEST_F(LobCWAJTestFixture, BoatrightRightHandSpinRightwardWind) { {0, 3071, 5230, -2.00, 0.00, 0.000}, {150, 2992, 4963, 0.42, 0.19, 0.049}, {300, 2913, 4706, 1.87, 0.75, 0.100}, - {600, 2759, 4221, 1.63, 3.07, 0.206}, - {900, 2609, 3775, -3.18, 7.09, 0.318}, - {1200, 2464, 3366, -13.09, 12.94, 0.436}, - {1500, 2323, 2992, -28.74, 20.81, 0.562}, - {1800, 2187, 2652, -50.84, 30.86, 0.695}, - {2100, 2056, 2344, -80.21, 43.30, 0.836}, - {2400, 1929, 2064, -117.84, 58.36, 0.987}, - {2700, 1806, 1810, -164.80, 76.31, 1.148}, - {3000, 1687, 1579, -222.42, 97.46, 1.319}, - {4500, 1150, 734, -737.85, 266.99, 2.397}, - {6000, 949, 500, -1903.39, 555.46, 3.868}}; + {600, 2759, 4221, 1.63, 2.87, 0.206}, + {900, 2609, 3775, -3.18, 6.63, 0.318}, + {1200, 2464, 3366, -13.09, 12.29, 0.436}, + {1500, 2323, 2992, -28.74, 19.94, 0.562}, + {1800, 2187, 2652, -50.84, 29.76, 0.695}, + {2100, 2056, 2344, -80.21, 41.94, 0.836}, + {2400, 1929, 2064, -117.84, 56.71, 0.987}, + {2700, 1806, 1810, -164.80, 74.34, 1.148}, + {3000, 1687, 1579, -222.42, 95.14, 1.319}, + {4500, 1150, 734, -737.85, 261.60, 2.397}, + {6000, 949, 500, -1903.39, 545.68, 3.868}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -624,17 +624,17 @@ TEST_F(LobCWAJTestFixture, BoatrightLeftHandSpinRightwardWind) { {0, 3071, 5230, -2.00, 0.00, 0.000}, {150, 2992, 4963, 1.48, 0.15, 0.049}, {300, 2913, 4706, 3.99, 0.63, 0.100}, - {600, 2759, 4221, 5.89, 2.60, 0.206}, - {900, 2609, 3775, 3.21, 6.05, 0.318}, - {1200, 2464, 3366, -4.58, 11.09, 0.436}, - {1500, 2323, 2992, -18.10, 17.87, 0.562}, - {1800, 2187, 2652, -38.07, 26.52, 0.695}, - {2100, 2056, 2344, -65.32, 37.21, 0.836}, - {2400, 1929, 2064, -100.82, 50.11, 0.987}, - {2700, 1806, 1810, -145.65, 65.44, 1.148}, - {3000, 1687, 1579, -201.15, 83.43, 1.319}, - {4500, 1150, 734, -705.94, 225.13, 2.397}, - {6000, 949, 500, -1860.74, 455.01, 3.867}}; + {600, 2759, 4221, 5.89, 2.71, 0.206}, + {900, 2609, 3775, 3.21, 6.50, 0.318}, + {1200, 2464, 3366, -4.58, 11.92, 0.436}, + {1500, 2323, 2992, -18.10, 18.95, 0.562}, + {1800, 2187, 2652, -38.07, 27.88, 0.695}, + {2100, 2056, 2344, -65.32, 38.87, 0.836}, + {2400, 1929, 2064, -100.82, 52.10, 0.987}, + {2700, 1806, 1810, -145.65, 67.79, 1.148}, + {3000, 1687, 1579, -201.15, 86.20, 1.319}, + {4500, 1150, 734, -705.94, 231.13, 2.397}, + {6000, 949, 500, -1860.74, 466.23, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -662,7 +662,6 @@ TEST_F(LobCWAJTestFixture, BoatrightLeftHandSpinRightwardWind) { kTimeOfFlightError); } } -*/ struct Shot { double diameter; diff --git a/test/source/lob_spin_drift_test.cpp b/test/source/lob_spin_drift_test.cpp index e6460c3..d6b6c44 100644 --- a/test/source/lob_spin_drift_test.cpp +++ b/test/source/lob_spin_drift_test.cpp @@ -390,19 +390,19 @@ TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDrift) { 1800, 2100, 2400, 2700, 3000, 4500, 6000}; const std::vector kExpected = { {0, 3071, 5230, -2.00, 0.00, 0.000}, - {150, 2992, 4963, 0.95, 0.02, 0.049}, - {300, 2913, 4706, 2.93, 0.06, 0.100}, - {600, 2759, 4221, 3.76, 0.23, 0.206}, - {900, 2609, 3775, 0.02, 0.52, 0.318}, - {1200, 2464, 3366, -8.84, 0.93, 0.436}, - {1500, 2323, 2992, -23.42, 1.47, 0.562}, - {1800, 2187, 2652, -44.45, 2.17, 0.695}, - {2100, 2056, 2344, -72.77, 3.05, 0.836}, - {2400, 1929, 2064, -109.33, 4.12, 0.987}, - {2700, 1806, 1810, -155.23, 5.44, 1.148}, - {3000, 1687, 1579, -211.78, 7.02, 1.319}, - {4500, 1150, 734, -721.84, 20.93, 2.397}, - {6000, 949, 500, -1882.01, 50.22, 3.867}}; + {150, 2992, 4963, 0.95, 0.00, 0.049}, + {300, 2913, 4706, 2.93, 0.00, 0.100}, + {600, 2759, 4221, 3.76, 0.00, 0.206}, + {900, 2609, 3775, 0.02, 0.00, 0.318}, + {1200, 2464, 3366, -8.84, 0.19, 0.436}, + {1500, 2323, 2992, -23.42, 0.49, 0.562}, + {1800, 2187, 2652, -44.45, 0.94, 0.695}, + {2100, 2056, 2344, -72.77, 1.54, 0.836}, + {2400, 1929, 2064, -109.33, 2.31, 0.987}, + {2700, 1806, 1810, -155.23, 3.27, 1.148}, + {3000, 1687, 1579, -211.78, 4.47, 1.319}, + {4500, 1150, 734, -721.84, 15.23, 2.397}, + {6000, 949, 500, -1882.01, 39.72, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -423,14 +423,14 @@ TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDrift) { lob::InchToMoa(solutions.at(i).deflection, solutions.at(i).range); const double kExpectedDeflectionMoa = lob::InchToMoa(kExpected.at(i).deflection, kExpected.at(i).range); - //EXPECT_NEAR(kSolutionDeflectionMoa, kExpectedDeflectionMoa, kMoaError); + EXPECT_NEAR(kSolutionDeflectionMoa, kExpectedDeflectionMoa, kMoaError); EXPECT_NEAR(solutions.at(i).deflection, kExpected.at(i).deflection, kInchError); EXPECT_NEAR(solutions.at(i).time_of_flight, kExpected.at(i).time_of_flight, kTimeOfFlightError); } } -/* + // NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDriftFast) { ASSERT_NE(puut, nullptr); @@ -454,19 +454,19 @@ TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDriftFast) { 1800, 2100, 2400, 2700, 3000, 4500, 6000}; const std::vector kExpected = { {0, 3071, 5230, -2.00, 0.00, 0.000}, - {150, 2992, 4963, 0.95, 0.02, 0.049}, - {300, 2913, 4706, 2.93, 0.08, 0.100}, - {600, 2759, 4221, 3.76, 0.29, 0.206}, - {900, 2609, 3775, 0.02, 0.64, 0.318}, - {1200, 2464, 3366, -8.84, 1.15, 0.436}, - {1500, 2323, 2992, -23.42, 1.83, 0.562}, - {1800, 2187, 2652, -44.45, 2.70, 0.695}, - {2100, 2056, 2344, -72.77, 3.79, 0.836}, - {2400, 1929, 2064, -109.33, 5.13, 0.987}, - {2700, 1806, 1810, -155.23, 6.76, 1.148}, - {3000, 1687, 1579, -211.78, 8.72, 1.319}, - {4500, 1150, 734, -721.84, 26.01, 2.397}, - {6000, 949, 500, -1882.01, 62.42, 3.867}}; + {150, 2992, 4963, 0.95, 0.00, 0.049}, + {300, 2913, 4706, 2.93, 0.00, 0.100}, + {600, 2759, 4221, 3.76, 0.00, 0.206}, + {900, 2609, 3775, 0.02, 0.00, 0.318}, + {1200, 2464, 3366, -8.84, 0.23, 0.436}, + {1500, 2323, 2992, -23.42, 0.607, 0.562}, + {1800, 2187, 2652, -44.45, 1.15, 0.695}, + {2100, 2056, 2344, -72.77, 1.89, 0.836}, + {2400, 1929, 2064, -109.33, 2.83, 0.987}, + {2700, 1806, 1810, -155.23, 4.02, 1.148}, + {3000, 1687, 1579, -211.78, 5.49, 1.319}, + {4500, 1150, 734, -721.84, 18.70, 2.397}, + {6000, 949, 500, -1882.01, 48.77, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -518,19 +518,19 @@ TEST_F(LobSpinTestFixture, BoatrightLeftHandSpinDrift) { 1800, 2100, 2400, 2700, 3000, 4500, 6000}; const std::vector kExpected = { {0, 3071, 5230, -2.00, 0.00, 0.000}, - {150, 2992, 4963, 0.95, -0.02, 0.049}, - {300, 2913, 4706, 2.93, -0.06, 0.100}, - {600, 2759, 4221, 3.76, -0.23, 0.206}, - {900, 2609, 3775, 0.02, -0.52, 0.318}, - {1200, 2464, 3366, -8.84, -0.93, 0.436}, - {1500, 2323, 2992, -23.42, -1.47, 0.562}, - {1800, 2187, 2652, -44.45, -2.17, 0.695}, - {2100, 2056, 2344, -72.77, -3.05, 0.836}, - {2400, 1929, 2064, -109.33, -4.12, 0.987}, - {2700, 1806, 1810, -155.23, -5.44, 1.148}, - {3000, 1687, 1579, -211.78, -7.02, 1.319}, - {4500, 1150, 734, -721.84, -20.93, 2.397}, - {6000, 949, 500, -1882.01, -50.22, 3.867}}; + {150, 2992, 4963, 0.95, -0.00, 0.049}, + {300, 2913, 4706, 2.93, -0.00, 0.100}, + {600, 2759, 4221, 3.76, -0.00, 0.206}, + {900, 2609, 3775, 0.02, -0.00, 0.318}, + {1200, 2464, 3366, -8.84, -0.19, 0.436}, + {1500, 2323, 2992, -23.42, -0.49, 0.562}, + {1800, 2187, 2652, -44.45, -0.94, 0.695}, + {2100, 2056, 2344, -72.77, -1.54, 0.836}, + {2400, 1929, 2064, -109.33, -2.31, 0.987}, + {2700, 1806, 1810, -155.23, -3.27, 1.148}, + {3000, 1687, 1579, -211.78, -4.47, 1.319}, + {4500, 1150, 734, -721.84, -15.23, 2.397}, + {6000, 949, 500, -1882.01, -39.72, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -582,19 +582,19 @@ TEST_F(LobSpinTestFixture, BoatrightLeftHandSpinDriftFast) { 1800, 2100, 2400, 2700, 3000, 4500, 6000}; const std::vector kExpected = { {0, 3071, 5230, -2.00, 0.00, 0.000}, - {150, 2992, 4963, 0.95, -0.02, 0.049}, - {300, 2913, 4706, 2.93, -0.08, 0.100}, - {600, 2759, 4221, 3.76, -0.29, 0.206}, - {900, 2609, 3775, 0.02, -0.64, 0.318}, - {1200, 2464, 3366, -8.84, -1.15, 0.436}, - {1500, 2323, 2992, -23.42, -1.83, 0.562}, - {1800, 2187, 2652, -44.45, -2.70, 0.695}, - {2100, 2056, 2344, -72.77, -3.79, 0.836}, - {2400, 1929, 2064, -109.33, -5.13, 0.987}, - {2700, 1806, 1810, -155.23, -6.76, 1.148}, - {3000, 1687, 1579, -211.78, -8.72, 1.319}, - {4500, 1150, 734, -721.84, -26.01, 2.397}, - {6000, 949, 500, -1882.01, -62.42, 3.867}}; + {150, 2992, 4963, 0.95, -0.00, 0.049}, + {300, 2913, 4706, 2.93, -0.00, 0.100}, + {600, 2759, 4221, 3.76, -0.00, 0.206}, + {900, 2609, 3775, 0.02, -0.00, 0.318}, + {1200, 2464, 3366, -8.84, -0.23, 0.436}, + {1500, 2323, 2992, -23.42, -0.607, 0.562}, + {1800, 2187, 2652, -44.45, -1.15, 0.695}, + {2100, 2056, 2344, -72.77, -1.89, 0.836}, + {2400, 1929, 2064, -109.33, -2.83, 0.987}, + {2700, 1806, 1810, -155.23, -4.02, 1.148}, + {3000, 1687, 1579, -211.78, -5.49, 1.319}, + {4500, 1150, 734, -721.84, -18.70, 2.397}, + {6000, 949, 500, -1882.01, -48.77, 3.867}}; std::array solutions = {}; const size_t kSize = lob::Solve(kInput, kRanges, solutions); @@ -621,7 +621,7 @@ TEST_F(LobSpinTestFixture, BoatrightLeftHandSpinDriftFast) { EXPECT_NEAR(solutions.at(i).time_of_flight, kExpected.at(i).time_of_flight, kTimeOfFlightError); } -}*/ +} } // namespace tests From ba95b48c5ce0f007f9540e1ea96d66e2d90f72a2 Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Sun, 28 Dec 2025 21:56:05 -0600 Subject: [PATCH 13/14] remove debug include --- source/lob_solve.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/source/lob_solve.cpp b/source/lob_solve.cpp index 47820e7..279ff3e 100644 --- a/source/lob_solve.cpp +++ b/source/lob_solve.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "calc.hpp" #include "cartesian.hpp" From 778a3f1d9c0affb5e54b508915a65b9ee76da7bd Mon Sep 17 00:00:00 2001 From: Joel Benway <157863269+joelbenway@users.noreply.github.com> Date: Sun, 28 Dec 2025 21:56:26 -0600 Subject: [PATCH 14/14] typo --- test/source/lob_spin_drift_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/source/lob_spin_drift_test.cpp b/test/source/lob_spin_drift_test.cpp index d6b6c44..ffc1e3e 100644 --- a/test/source/lob_spin_drift_test.cpp +++ b/test/source/lob_spin_drift_test.cpp @@ -459,7 +459,7 @@ TEST_F(LobSpinTestFixture, BoatrightRightHandSpinDriftFast) { {600, 2759, 4221, 3.76, 0.00, 0.206}, {900, 2609, 3775, 0.02, 0.00, 0.318}, {1200, 2464, 3366, -8.84, 0.23, 0.436}, - {1500, 2323, 2992, -23.42, 0.607, 0.562}, + {1500, 2323, 2992, -23.42, 0.61, 0.562}, {1800, 2187, 2652, -44.45, 1.15, 0.695}, {2100, 2056, 2344, -72.77, 1.89, 0.836}, {2400, 1929, 2064, -109.33, 2.83, 0.987}, @@ -587,7 +587,7 @@ TEST_F(LobSpinTestFixture, BoatrightLeftHandSpinDriftFast) { {600, 2759, 4221, 3.76, -0.00, 0.206}, {900, 2609, 3775, 0.02, -0.00, 0.318}, {1200, 2464, 3366, -8.84, -0.23, 0.436}, - {1500, 2323, 2992, -23.42, -0.607, 0.562}, + {1500, 2323, 2992, -23.42, -0.61, 0.562}, {1800, 2187, 2652, -44.45, -1.15, 0.695}, {2100, 2056, 2344, -72.77, -1.89, 0.836}, {2400, 1929, 2064, -109.33, -2.83, 0.987},