From 7b6d28de0c33cd5e7aa94c03e877b45965dd3449 Mon Sep 17 00:00:00 2001 From: Andrew Coffey <49015102+oddbookworm@users.noreply.github.com> Date: Sun, 14 Jan 2024 04:29:41 -0600 Subject: [PATCH] lots of 'minor' fixes lol, plus a huge overhaul of arithmetic and vectors (#10) * lots of 'minor' fixes lol, plus a huge overhaul of arithmetic and vectors * fix const-qualified member preventing copy construction * fixed implicit cast warnings * fixed more implicit conversion warnings --- CMakeLists.txt | 6 +- include/Constants.hpp | 3 + include/Draw.hpp | 2 +- include/Entity.hpp | 2 +- include/Font.hpp | 2 +- include/Input.hpp | 8 +- include/Math.hpp | 123 +++++++++++++--- include/MathOverflow.hpp | 75 ++++++++++ include/Mixer.hpp | 2 +- include/TileMap.hpp | 5 +- include/Time.hpp | 11 +- src/MathOverflow.cpp | 273 ++++++++++++++++++++++++++++++++++++ src/entity.cpp | 3 +- src/font.cpp | 9 +- src/math.cpp | 96 ++++++++++--- src/mixer.cpp | 16 ++- src/render_window.cpp | 6 +- src/time.cpp | 22 +-- test/main.cpp | 8 -- test/test_MathOverflow.cpp | 278 +++++++++++++++++++++++++++++++++++++ 20 files changed, 865 insertions(+), 85 deletions(-) create mode 100644 include/MathOverflow.hpp create mode 100644 src/MathOverflow.cpp delete mode 100644 test/main.cpp create mode 100644 test/test_MathOverflow.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7127a34..8a09a4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,12 +20,12 @@ add_executable(${TESTING} EXCLUDE_FROM_ALL ${SOURCES} ${TESTS}) target_compile_definitions( ${LIBRARY} PUBLIC LOG_LEVEL=${LOG_LEVEL} ) target_compile_definitions( ${TESTING} PUBLIC LOG_LEVEL=${LOG_LEVEL} ) -set_target_properties( ${LIBRARY} PROPERTIES RUNTIME_OUTPUT_DIRECTORY +set_target_properties( ${LIBRARY} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin ) set_target_properties( ${LIBRARY} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin ) -set_target_properties( ${TESTING} PROPERTIES RUNTIME_OUTPUT_DIRECTORY +set_target_properties( ${TESTING} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin ) set_target_properties( ${TESTING} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin ) @@ -204,7 +204,7 @@ if (WIN32) endif() target_link_libraries(${LIBRARY} PRIVATE SDL2 SDL2_image SDL2_ttf SDL2_mixer tmxlite) -target_link_libraries(${TESTING} PRIVATE SDL2 SDL2_image SDL2_ttf SDL2_mixer tmxlite gtest) +target_link_libraries(${TESTING} PRIVATE SDL2 SDL2_image SDL2_ttf SDL2_mixer tmxlite gtest gtest_main) target_include_directories(${LIBRARY} PRIVATE ${CMAKE_SOURCE_DIR}/include) target_include_directories(${TESTING} PRIVATE ${CMAKE_SOURCE_DIR}/include) diff --git a/include/Constants.hpp b/include/Constants.hpp index bda6b61..cc3ac56 100644 --- a/include/Constants.hpp +++ b/include/Constants.hpp @@ -5,6 +5,9 @@ namespace kn { +typedef float float32_t; +typedef double float64_t; + typedef SDL_Scancode KEYS; typedef SDL_Event Event; typedef SDL_Color Color; diff --git a/include/Draw.hpp b/include/Draw.hpp index 0be32ef..7fba83c 100644 --- a/include/Draw.hpp +++ b/include/Draw.hpp @@ -1,7 +1,7 @@ #pragma once -#include "Rect.hpp" #include "Constants.hpp" +#include "Rect.hpp" namespace kn { diff --git a/include/Entity.hpp b/include/Entity.hpp index 4e779e2..e2b646c 100644 --- a/include/Entity.hpp +++ b/include/Entity.hpp @@ -34,7 +34,7 @@ class Entity */ std::shared_ptr getTexture() const; - Rect crop = {0, 0, 0, 0}; + Rect crop; Rect rect; math::Vec2 position; math::Vec2 direction; diff --git a/include/Font.hpp b/include/Font.hpp index 7297c03..937d7d4 100644 --- a/include/Font.hpp +++ b/include/Font.hpp @@ -5,8 +5,8 @@ #include -#include "Texture.hpp" #include "Constants.hpp" +#include "Texture.hpp" namespace kn { diff --git a/include/Input.hpp b/include/Input.hpp index 8616a64..b29a799 100644 --- a/include/Input.hpp +++ b/include/Input.hpp @@ -4,8 +4,8 @@ #include -#include "Math.hpp" #include "Constants.hpp" +#include "Math.hpp" namespace kn { @@ -43,10 +43,8 @@ const Uint8* getKeysPressed(); * * @return The vector of the keys pressed. */ -math::Vec2 getVector(const std::vector& left = {}, - const std::vector& right = {}, - const std::vector& up = {}, - const std::vector& down = {}); +math::Vec2 getVector(const std::vector& left = {}, const std::vector& right = {}, + const std::vector& up = {}, const std::vector& down = {}); } // namespace input } // namespace kn diff --git a/include/Math.hpp b/include/Math.hpp index 735db4f..1dc6e41 100644 --- a/include/Math.hpp +++ b/include/Math.hpp @@ -1,23 +1,40 @@ #pragma once +#include "ErrorLogger.hpp" +#include "MathOverflow.hpp" + namespace kn { + +using Overflow::closeToZero; +using Overflow::isProductValid; +using Overflow::isSumValid; + namespace math { /** * @brief A 2D vector. */ -struct Vec2 +class Vec2 { - float x = 0.0f; - float y = 0.0f; + public: + double x; + double y; + + Vec2(); + + template + Vec2(_first x, _second y) + : x(static_cast(y)), y(static_cast(y)), tolerance(0.0001) + { + } - Vec2() = default; - Vec2(float x, float y); - Vec2(int x, int y); - Vec2(float x, int y); - Vec2(int x, float y); + template + Vec2(_first x, _second y, double tolerance) + : x(static_cast(y)), y(static_cast(y)), tolerance(tolerance) + { + } /** * @brief Get a vector with all components set to 0. @@ -28,31 +45,69 @@ struct Vec2 /** * @brief Get the length of the vector. - * @return The length of the vector. + * @return The length of the vector if no overflow happens, otherwise -1.0 */ - float getLength() const; + double getLength() const; /** - * @brief Normalize the vector. + * @brief Normalize the vector in-place. Fails if an overflow occurs or the vector is the zero + * vector */ - void normalize(); + bool normalize(); /** * @brief Get the distance to another vector. * * @param other The other vector. * - * @return The distance to another vector. + * @return The distance to another vector if no overflow happens, otherwise -1.0 */ - float distanceTo(const Vec2& other) const; + double distanceTo(const Vec2& other) const; - Vec2 operator*(float scalar) const; - Vec2 operator/(float scalar) const; + template Vec2 operator/(T scalar) const { return Vec2(x / scalar, y / scalar); } + + /** + * @brief Adds two vectors + * + * @param other the other vector + * @return a vector sum of the other two, or the zero vector on overflow + */ Vec2 operator+(const Vec2& other) const; + + /** + * @brief Subtracts two vectors + * + * @param other the subtracted vector + * @return a vector difference of the other two, or the zero vector on overflow + */ Vec2 operator-(const Vec2& other) const; - Vec2 operator+=(const Vec2& other); + + /** + * @brief in-place addition of another vector to this + * + * @param other the other vector + * @return reference to *this + */ + Vec2& operator+=(const Vec2& other); + + /** + * @brief Checks if two vectors are the same + * + * @param other the vector to compare to + * @return true if the vectors are close to the same, false otherwise + */ bool operator==(const Vec2& other) const; + + /** + * @brief Checks if two vectors are different + * + * @param other the vector to compare to + * @return true if the vectors are not close to the same, false otherwise + */ bool operator!=(const Vec2& other) const; + + protected: + double tolerance; //!< the accuracy with which comparisons are made }; /** @@ -75,7 +130,39 @@ Vec2 clampVec(Vec2 vec, const Vec2& min, const Vec2& max); * * @return The interpolated vector. */ -Vec2 lerpVec(const Vec2& a, const Vec2& b, float t); +Vec2 lerpVec(const Vec2& a, const Vec2& b, double t); + +/** + * @brief Multiplies a vector by a constant + * + * @tparam T type of the constant + * @param lhs the constant + * @param rhs the vector + * @return A vector scaled by the constant + */ +template Vec2 operator*(const T& lhs, const Vec2& rhs) +{ + if (!isProductValid(lhs, rhs.x) || !isProductValid(lhs, rhs.y)) + { + WARN("Multiplication would result in overflow"); + return Vec2::ZERO(); + } + + const double x = lhs * rhs.x; + const double y = lhs * rhs.y; + + return Vec2(x, y); +} + +/** + * @brief Multiplies a vector by a constant + * + * @tparam T type of the constant + * @param lhs the vector + * @param rhs the constant + * @return A vector scaled by the constant + */ +template Vec2 operator*(const Vec2& lhs, const T& rhs) { return rhs * lhs; } } // namespace math } // namespace kn diff --git a/include/MathOverflow.hpp b/include/MathOverflow.hpp new file mode 100644 index 0000000..114f0bd --- /dev/null +++ b/include/MathOverflow.hpp @@ -0,0 +1,75 @@ +#ifndef MATH_OVERFLOW_HPP +#define MATH_OVERFLOW_HPP + +#include +#include +#include + +#include "Constants.hpp" + +namespace kn +{ + +namespace Overflow +{ + +//@{ +/** + * @brief Checks if an overflow would occur in summation + * + * @param first first value + * @param second second value + * @return true if sum is valid, false otherwise + */ +bool isSumValid(const float64_t& first, const float64_t& second); + +bool isSumValid(const float32_t& first, const float32_t& second); + +bool isSumValid(const uint32_t& first, const uint32_t& second); + +bool isSumValid(const uint64_t& first, const uint64_t& second); + +bool isSumValid(const int32_t& first, const int32_t& second); + +bool isSumValid(const int64_t& first, const int64_t& second); +//@} + +//@{ +/** + * @brief Checks if an overflow would occur in multiplcation + * + * @param first first value + * @param second second value + * @return true if product is valid, false otherwise + */ +bool isProductValid(const float64_t& first, const float64_t& second); + +bool isProductValid(const float32_t& first, const float32_t& second); + +bool isProductValid(const uint32_t& first, const uint32_t& second); + +bool isProductValid(const uint64_t& first, const uint64_t& second); + +bool isProductValid(const int32_t& first, const int32_t& second); + +bool isProductValid(const int64_t& first, const int64_t& second); +//@} + +//@{ +/** + * @brief Checks if the value is close to zero + * + * @param value value to check + * @param tolerance the accuracy to use, anything closer to 0 than this will be considered zero + * @return true if close to zero, false otherwise + */ +bool closeToZero(const float64_t& value, const float64_t tolerance = 0.0001); + +bool closeToZero(const float32_t& value, const float32_t tolerance = 0.0001f); +//@} + +} // namespace Overflow + +} // namespace kn + +#endif // MATH_OVERFLOW_HPP \ No newline at end of file diff --git a/include/Mixer.hpp b/include/Mixer.hpp index 48c7d0c..3c2adea 100644 --- a/include/Mixer.hpp +++ b/include/Mixer.hpp @@ -34,7 +34,7 @@ class Sound final * @param loops The number of times to loop the sound. * @param maxMs The number of milliseconds to play the sound. * @param fadeMs The number of milliseconds to fade in. - * + * * @warning Fade in is currently not working. */ void play(int loops = 0, int maxMs = -1, int fadeMs = 0); diff --git a/include/TileMap.hpp b/include/TileMap.hpp index 89b3cc5..84a762d 100644 --- a/include/TileMap.hpp +++ b/include/TileMap.hpp @@ -25,8 +25,9 @@ struct Tile /** * @brief A class that represents a Tiled map. - * - * @warning This class is currently only compatible with orthogonal maps and tile layers (no objects). + * + * @warning This class is currently only compatible with orthogonal maps and tile layers (no + * objects). */ class TileMap final { diff --git a/include/Time.hpp b/include/Time.hpp index bcd1b51..39c4b12 100644 --- a/include/Time.hpp +++ b/include/Time.hpp @@ -15,7 +15,7 @@ namespace time class Clock final { public: - Clock() = default; + Clock(); ~Clock() = default; /** @@ -28,9 +28,12 @@ class Clock final double tick(int frameRate = 60); private: - double frameTime, targetFrameTime, deltaTime; - double frequency = SDL_GetPerformanceFrequency(); - double now, last = SDL_GetPerformanceCounter(); + double m_frameTime; + double m_targetFrameTime; + double m_deltaTime; + double m_frequency; + double m_now; + double m_last; }; } // namespace time diff --git a/src/MathOverflow.cpp b/src/MathOverflow.cpp new file mode 100644 index 0000000..a8dd751 --- /dev/null +++ b/src/MathOverflow.cpp @@ -0,0 +1,273 @@ +#include "MathOverflow.hpp" + +namespace kn +{ + +namespace Overflow +{ + +bool isSumValid(const float64_t& first, const float64_t& second) +{ + if (first >= 0) + { + // check for overflowing the float64_t maximum + if (second >= 0 && (first > std::numeric_limits::max() - second)) + { + return false; + } + } + else + { + // check for overflowing the negative of the float64_t max + // note that std::numeric_limits::min() doesn't return a negative + // but instead returns the minimum representable value that is as close to + // zero as doubles can get without becoming zero + if (second < 0 && (first < -std::numeric_limits::max() - second)) + { + return false; + } + } + + return true; +} + +bool isSumValid(const float32_t& first, const float32_t& second) +{ + if (first >= 0) + { + // check for overflowing the float32_t maximum + if (second >= 0 && (first > std::numeric_limits::max() - second)) + { + return false; + } + } + else + { + // check for overflowing the negative of the float32_t max + // note that std::numeric_limits::min() doesn't return a negative + // but instead returns the minimum representable value that is as close to + // zero as floats can get without becoming zero + if (second < 0 && (first < -std::numeric_limits::max() - second)) + { + return false; + } + } + return true; +} + +bool isSumValid(const uint32_t& first, const uint32_t& second) +{ + if (first > std::numeric_limits::max() - second) + { + return false; + } + return true; +} + +bool isSumValid(const uint64_t& first, const uint64_t& second) +{ + if (first > std::numeric_limits::max() - second) + { + return false; + } + return true; +} + +bool isSumValid(const int32_t& first, const int32_t& second) +{ + if (first >= 0) + { + // check for overflowing the int32_t maximum + if (second >= 0 && (first > std::numeric_limits::max() - second)) + { + return false; + } + } + else + { + // check for overflowing the int32_t min + if (second < 0 && (first < std::numeric_limits::min() - second)) + { + return false; + } + } + return true; +} + +bool isSumValid(const int64_t& first, const int64_t& second) +{ + if (first >= 0) + { + // check for overflowing the int64_t maximum + if (second >= 0 && (first > std::numeric_limits::max() - second)) + { + return false; + } + } + else + { + // check for overflowing the int64_t min + if (second < 0 && (first < std::numeric_limits::min() - second)) + { + return false; + } + } + return true; +} + +bool isProductValid(const float64_t& first, const float64_t& second) +{ + if (!std::isfinite(first) || !std::isfinite(second)) + { + // one of them is NaN or inf + return false; + } + + float64_t absFirst = std::fabs(first); + float64_t absSecond = std::fabs(second); + + if (absFirst > 1.0 && absSecond > 1.0) + { + if (absFirst > std::numeric_limits::max() / absSecond) + { + return false; + } + } + + return true; +} + +bool isProductValid(const float32_t& first, const float32_t& second) +{ + if (!std::isfinite(first) || !std::isfinite(second)) + { + // one of them is NaN or inf + return false; + } + + float32_t absFirst = std::fabs(first); + float32_t absSecond = std::fabs(second); + + if (absFirst > 1.0 && absSecond > 1.0) + { + if (absFirst > std::numeric_limits::max() / absSecond) + { + return false; + } + } + + return true; +} + +bool isProductValid(const uint32_t& first, const uint32_t& second) +{ + if (first > 0U && second > 0U) + { + if (first > std::numeric_limits::max() / second) + { + return false; + } + } + + return true; +} + +bool isProductValid(const uint64_t& first, const uint64_t& second) +{ + if (first > 0U && second > 0U) + { + if (first > std::numeric_limits::max() / second) + { + return false; + } + } + + return true; +} + +bool isProductValid(const int32_t& first, const int32_t& second) +{ + if (first != 0 && second != 0) + { + if ((first > 0 && second > 0) || (first < 0 && second < 0)) + { + // result is positive, so check max + if (first > std::numeric_limits::max() / second) + { + return false; + } + else if (first > 0) + { + if (second < -std::numeric_limits::max() / first) + { + return false; + } + } + else + { + if (first < -std::numeric_limits::max() / second) + { + return false; + } + } + } + } + + return true; +} + +bool isProductValid(const int64_t& first, const int64_t& second) +{ + if (first != 0 && second != 0) + { + if ((first > 0 && second > 0) || (first < 0 && second < 0)) + { + // result is positive, so check max + if (first > std::numeric_limits::max() / second) + { + return false; + } + else if (first > 0) + { + // must divide by positive + if (second < std::numeric_limits::min() / first) + { + return false; + } + } + else + { + // must divide by positive + if (first < std::numeric_limits::min() / second) + { + return false; + } + } + } + } + + return true; +} + +bool closeToZero(const float64_t& value, const float64_t tolerance) +{ + if (std::fabs(value) < tolerance) + { + return true; + } + + return false; +} + +bool closeToZero(const float32_t& value, const float32_t tolerance) +{ + if (std::fabs(value) < tolerance) + { + return true; + } + + return false; +} + +} // namespace Overflow +} // namespace kn \ No newline at end of file diff --git a/src/entity.cpp b/src/entity.cpp index ec5621f..0604d5c 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -3,7 +3,8 @@ namespace kn { -Entity::Entity(std::shared_ptr texture) : rect(texture->getRect()), texture(texture) +Entity::Entity(std::shared_ptr texture) + : crop({0, 0, 0, 0}), rect(texture->getRect()), texture(texture) { crop.w = rect.w; crop.h = rect.h; diff --git a/src/font.cpp b/src/font.cpp index 31337c4..354cecc 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -1,5 +1,5 @@ -#include "ErrorLogger.hpp" #include "Font.hpp" +#include "ErrorLogger.hpp" #include "RenderWindow.hpp" namespace kn @@ -25,11 +25,10 @@ std::shared_ptr Font::render(const std::string& text, bool antialias, S surface = TTF_RenderUTF8_Blended_Wrapped(font, text.c_str(), color, wrapLength); else surface = TTF_RenderUTF8_Blended(font, text.c_str(), color); + else if (wrapLength > 0) + surface = TTF_RenderText_Solid_Wrapped(font, text.c_str(), color, wrapLength); else - if (wrapLength > 0) - surface = TTF_RenderText_Solid_Wrapped(font, text.c_str(), color, wrapLength); - else - surface = TTF_RenderText_Solid(font, text.c_str(), color); + surface = TTF_RenderText_Solid(font, text.c_str(), color); if (surface == nullptr) { diff --git a/src/math.cpp b/src/math.cpp index 98d5f01..0996f67 100644 --- a/src/math.cpp +++ b/src/math.cpp @@ -1,43 +1,84 @@ #include #include +#include "ErrorLogger.hpp" #include "Math.hpp" +#include "MathOverflow.hpp" namespace kn { + +using Overflow::isProductValid; +using Overflow::isSumValid; + namespace math { -Vec2::Vec2(float x, float y) : x(x), y(y) {} -Vec2::Vec2(int x, int y) : x(static_cast(x)), y(static_cast(y)) {} -Vec2::Vec2(float x, int y) : x(x), y(static_cast(y)) {} -Vec2::Vec2(int x, float y) : x(static_cast(x)), y(y) {} +Vec2::Vec2() : x(0.0), y(0.0), tolerance(0.0001) {} -Vec2 Vec2::ZERO() { return {0.0f, 0.0f}; } +Vec2 Vec2::ZERO() { return Vec2(); } -float Vec2::getLength() const { return sqrtf(x * x + y * y); } +double Vec2::getLength() const +{ + if (!isProductValid(x, x) || !isProductValid(y, y)) + { + WARN("Calculation of magnitude would result in overflow"); + return -1.0; + } + + const double first = x * x; + const double second = y * y; + + if (!isSumValid(first, second)) + { + WARN("Calculation of magnitude would result in overflow"); + return -1.0; + } + + return sqrt(first + second); +} -void Vec2::normalize() +bool Vec2::normalize() { - float c = getLength(); + const float c = getLength(); + if (c <= 0.0) + { + WARN("Cannot normalize vector either because it is the zero vector, or it would overflow"); + return false; + } + + const double factor = 1 / c; + if (!isProductValid(x, factor) || !isProductValid(y, factor)) + { + WARN("Cannot normalize vector due to overflow"); + return false; + } + x /= c; y /= c; + + return true; } -float Vec2::distanceTo(const Vec2& other) const +double Vec2::distanceTo(const Vec2& other) const { - return sqrtf(powf(other.x - x, 2) + powf(other.y - y, 2)); -} + if (!isSumValid(other.x, -x) || !isSumValid(other.y, -y)) + { + WARN("Calculation would result in overflow"); + return -1.0; + } -Vec2 Vec2::operator*(float scalar) const { return {x * scalar, y * scalar}; } + const double dx = other.x - x; + const double dy = other.y - y; -Vec2 Vec2::operator/(float scalar) const { return {x / scalar, y / scalar}; } + return Vec2(dx, dy).getLength(); +} -Vec2 Vec2::operator+(const Vec2& other) const { return {x + other.x, y + other.y}; } +Vec2 Vec2::operator+(const Vec2& other) const { return Vec2(x + other.x, y + other.y); } -Vec2 Vec2::operator-(const Vec2& other) const { return {x - other.x, y - other.y}; } +Vec2 Vec2::operator-(const Vec2& other) const { return *this + (-1.0 * other); } -Vec2 Vec2::operator+=(const Vec2& other) +Vec2& Vec2::operator+=(const Vec2& other) { x += other.x; y += other.y; @@ -45,7 +86,22 @@ Vec2 Vec2::operator+=(const Vec2& other) return *this; } -bool Vec2::operator==(const Vec2& other) const { return (x == other.x && y == other.y); } +bool Vec2::operator==(const Vec2& other) const +{ + // return (x == other.x && y == other.y); + if (!isSumValid(x, -other.x) || !isSumValid(y, -other.y)) + { + TRACE("Vector comparison would result in overflow, they can't be the same"); + return false; + } + + if (!closeToZero(x - other.x, tolerance) || !closeToZero(y - other.y, tolerance)) + { + return false; + } + + return true; +} bool Vec2::operator!=(const Vec2& other) const { return !(*this == other); } @@ -57,7 +113,11 @@ Vec2 clampVec(Vec2 vec, const Vec2& min, const Vec2& max) return vec; } -Vec2 lerpVec(const Vec2& a, const Vec2& b, float t) { return a + (b - a) * t; } +Vec2 lerpVec(const Vec2& a, const Vec2& b, double t) +{ + // TODO: figure out a way to signal if an overflow happens + return a + (b - a) * t; +} } // namespace math } // namespace kn diff --git a/src/mixer.cpp b/src/mixer.cpp index a2303a8..c2a0b9d 100644 --- a/src/mixer.cpp +++ b/src/mixer.cpp @@ -1,8 +1,8 @@ -#include #include +#include -#include "Mixer.hpp" #include "ErrorLogger.hpp" +#include "Mixer.hpp" namespace kn { @@ -10,15 +10,18 @@ namespace mixer { Sound::Sound(const std::string& fileDir) -{ +{ std::filesystem::path filePath(fileDir); std::string extension = filePath.extension().string(); - if (extension == ".ogg" || extension == ".mp3" || extension == ".wav") { + if (extension == ".ogg" || extension == ".mp3" || extension == ".wav") + { sound = Mix_LoadWAV(fileDir.c_str()); if (!sound) FATAL("Failed to load sound: " + fileDir); - } else { + } + else + { FATAL("Unsupported file format: " + fileDir); } @@ -26,7 +29,7 @@ Sound::Sound(const std::string& fileDir) } void Sound::play(int loops, int maxMs, int fadeMs) -{ +{ int channelNum = -1; if (fadeMs > 0) @@ -41,7 +44,6 @@ void Sound::play(int loops, int maxMs, int fadeMs) } Mix_Volume(channelNum, volume); - } void Sound::setVolume(float newVolume) diff --git a/src/render_window.cpp b/src/render_window.cpp index 1d9906f..8500b59 100644 --- a/src/render_window.cpp +++ b/src/render_window.cpp @@ -119,7 +119,8 @@ void RenderWindow::blit(const std::shared_ptr& texture, Rect crop, Rect void RenderWindow::blit(const std::shared_ptr& texture, const math::Vec2& position) { - SDL_FRect rect = {position.x, position.y, texture->getSize().x, texture->getSize().y}; + SDL_FRect rect = {(float)position.x, (float)position.y, (float)texture->getSize().x, + (float)texture->getSize().y}; SDL_RenderCopyF(m_renderer, texture->getSDLTexture(), nullptr, &rect); } @@ -166,7 +167,8 @@ void RenderWindow::blitEx(const std::shared_ptr& texture, const math::V flip = (SDL_RendererFlip)(flip | SDL_FLIP_VERTICAL); } - SDL_FRect rect = {position.x, position.y, texture->getSize().x, texture->getSize().y}; + SDL_FRect rect = {(float)position.x, (float)position.y, (float)texture->getSize().x, + (float)texture->getSize().y}; SDL_RenderCopyExF(m_renderer, texture->getSDLTexture(), nullptr, &rect, angle, nullptr, flip); } diff --git a/src/time.cpp b/src/time.cpp index a3f42cf..d563654 100644 --- a/src/time.cpp +++ b/src/time.cpp @@ -6,21 +6,27 @@ namespace kn namespace time { +Clock::Clock() + : m_frameTime(0), m_targetFrameTime(0), m_deltaTime(0), + m_frequency(SDL_GetPerformanceFrequency()), m_now(0), m_last(SDL_GetPerformanceCounter()) +{ +} + double Clock::tick(int frameRate) { if (frameRate < 1) frameRate = 1; - targetFrameTime = 1000.0 / frameRate; - frameTime = ((SDL_GetPerformanceCounter() / frequency) - (last / frequency)) * 1000.0; - if (frameTime < targetFrameTime) - SDL_Delay((uint32_t)(targetFrameTime - frameTime)); + m_targetFrameTime = 1000.0 / frameRate; + m_frameTime = ((SDL_GetPerformanceCounter() / m_frequency) - (m_last / m_frequency)) * 1000.0; + if (m_frameTime < m_targetFrameTime) + SDL_Delay((uint32_t)(m_targetFrameTime - m_frameTime)); - now = SDL_GetPerformanceCounter(); - deltaTime = (now / frequency) - (last / frequency); - last = now; + m_now = SDL_GetPerformanceCounter(); + m_deltaTime = (m_now / m_frequency) - (m_last / m_frequency); + m_last = m_now; - return deltaTime; + return m_deltaTime; } } // namespace time diff --git a/test/main.cpp b/test/main.cpp deleted file mode 100644 index ff90c4c..0000000 --- a/test/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "gtest/gtest.h" - -int main(int argc, char** argv) -{ - testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -} diff --git a/test/test_MathOverflow.cpp b/test/test_MathOverflow.cpp new file mode 100644 index 0000000..716a615 --- /dev/null +++ b/test/test_MathOverflow.cpp @@ -0,0 +1,278 @@ +#include + +#include "gtest/gtest.h" + +#include "Constants.hpp" +#include "MathOverflow.hpp" + +namespace +{ +using kn::float32_t; +using kn::float64_t; +using kn::Overflow::isProductValid; +using kn::Overflow::isSumValid; + +class DoubleOverflowTest : public ::testing::Test +{ + protected: + DoubleOverflowTest() : doubleMax(std::numeric_limits::max()) {} + + virtual ~DoubleOverflowTest() {} + + virtual void SetUp() {} + + virtual void TearDown() {} + + const float64_t doubleMax; +}; + +TEST_F(DoubleOverflowTest, SumOfDoubleMax) { EXPECT_FALSE(isSumValid(doubleMax, doubleMax)); } + +TEST_F(DoubleOverflowTest, SumOfNegativeDoubleMax) +{ + EXPECT_FALSE(isSumValid(-doubleMax, -doubleMax)); +} + +TEST_F(DoubleOverflowTest, SumOfHalfAndThreeFifthsDoubleMax) +{ + const float64_t half = doubleMax / 2; + const float64_t threeFifths = (double)3 / 5 * doubleMax; + EXPECT_FALSE(isSumValid(half, threeFifths)); +} + +TEST_F(DoubleOverflowTest, SumOfDoubleMaxAndNegativeDoubleMax) +{ + EXPECT_TRUE(isSumValid(doubleMax, -doubleMax)); +} + +TEST_F(DoubleOverflowTest, SumOfNegativeDoubleMaxAndDoubleMax) +{ + EXPECT_TRUE(isSumValid(-doubleMax, doubleMax)); +} + +TEST_F(DoubleOverflowTest, SumOfZeroAndDoubleMax) { EXPECT_TRUE(isSumValid(0.0, doubleMax)); } + +TEST_F(DoubleOverflowTest, SumOfDoubleMaxAndZero) { EXPECT_TRUE(isSumValid(doubleMax, 0.0)); } + +TEST_F(DoubleOverflowTest, SumOfTwoFifthsDoubleMax) +{ + const float64_t twoFifths = (double)2 / 5 * doubleMax; + + EXPECT_TRUE(isSumValid(twoFifths, twoFifths)); +} + +class FloatOverflowTest : public ::testing::Test +{ + protected: + FloatOverflowTest() : floatMax(std::numeric_limits::max()) {} + + virtual ~FloatOverflowTest() {} + + virtual void SetUp() {} + + virtual void TearDown() {} + + const float32_t floatMax; +}; + +TEST_F(FloatOverflowTest, SumOfFloatMax) { EXPECT_FALSE(isSumValid(floatMax, floatMax)); } + +TEST_F(FloatOverflowTest, SumOfNegativeFloatMax) { EXPECT_FALSE(isSumValid(-floatMax, -floatMax)); } + +TEST_F(FloatOverflowTest, SumOfHalfAndThreeFifthsFloatMax) +{ + const float32_t half = floatMax / 2; + const float32_t threeFifths = (float)3 / 5 * floatMax; + EXPECT_FALSE(isSumValid(half, threeFifths)); +} + +TEST_F(FloatOverflowTest, SumOfFloatMaxAndNegativeFloatMax) +{ + EXPECT_TRUE(isSumValid(floatMax, -floatMax)); +} + +TEST_F(FloatOverflowTest, SumOfNegativeFloatMaxAndFloatMax) +{ + EXPECT_TRUE(isSumValid(-floatMax, floatMax)); +} + +TEST_F(FloatOverflowTest, SumOfZeroAndFloatMax) { EXPECT_TRUE(isSumValid(0.0f, floatMax)); } + +TEST_F(FloatOverflowTest, SumOfFloatMaxAndZero) { EXPECT_TRUE(isSumValid(floatMax, 0.0f)); } + +TEST_F(FloatOverflowTest, SumOfTwoFifthsFloatMax) +{ + const float32_t twoFifths = (float)2 / 5 * floatMax; + + EXPECT_TRUE(isSumValid(twoFifths, twoFifths)); +} + +class UnsignedFourByteIntTest : public ::testing::Test +{ + protected: + UnsignedFourByteIntTest() + : max(std::numeric_limits::max()), min(std::numeric_limits::min()) + { + } + + virtual ~UnsignedFourByteIntTest() {} + + virtual void SetUp() {} + + virtual void TearDown() {} + + const uint32_t max; + const uint32_t min; +}; + +TEST_F(UnsignedFourByteIntTest, SumOfMaxValues) { EXPECT_FALSE(isSumValid(max, max)); } + +TEST_F(UnsignedFourByteIntTest, SumOfMinValues) { EXPECT_TRUE(isSumValid(min, min)); } + +TEST_F(UnsignedFourByteIntTest, SumOfHalfAndThreeFifthsMax) +{ + const uint32_t threeFifths = (double)3 / 5 * max; + const uint32_t half = (double)1 / 2 * max; + + EXPECT_FALSE(isSumValid(threeFifths, half)); +} + +TEST_F(UnsignedFourByteIntTest, SumOfTwoFifthsMax) +{ + const uint32_t twoFifths = (double)2 / 5 * max; + + EXPECT_TRUE(isSumValid(twoFifths, twoFifths)); +} + +class UnsignedEightByteIntTest : public ::testing::Test +{ + protected: + UnsignedEightByteIntTest() + : max(std::numeric_limits::max()), min(std::numeric_limits::min()) + { + } + + virtual ~UnsignedEightByteIntTest() {} + + virtual void SetUp() {} + + virtual void TearDown() {} + + const uint64_t max; + const uint64_t min; +}; + +TEST_F(UnsignedEightByteIntTest, SumOfMaxValues) { EXPECT_FALSE(isSumValid(max, max)); } + +TEST_F(UnsignedEightByteIntTest, SumOfMinValues) { EXPECT_TRUE(isSumValid(min, min)); } + +TEST_F(UnsignedEightByteIntTest, SumOfHalfAndThreeFifthsMax) +{ + const uint64_t threeFifths = (double)3 / 5 * max; + const uint64_t half = (double)1 / 2 * max; + + EXPECT_FALSE(isSumValid(threeFifths, half)); +} + +TEST_F(UnsignedEightByteIntTest, SumOfTwoFifthsMax) +{ + const uint64_t twoFifths = (double)2 / 5 * max; + + EXPECT_TRUE(isSumValid(twoFifths, twoFifths)); +} + +class SignedFourByteIntTest : public ::testing::Test +{ + protected: + SignedFourByteIntTest() + : max(std::numeric_limits::max()), min(std::numeric_limits::min()) + { + } + + virtual ~SignedFourByteIntTest() {} + + virtual void SetUp() {} + + virtual void TearDown() {} + + const int32_t max; + const int32_t min; +}; + +TEST_F(SignedFourByteIntTest, SumOfMaxValues) { EXPECT_FALSE(isSumValid(max, max)); } + +TEST_F(SignedFourByteIntTest, SumOfMinValues) { EXPECT_FALSE(isSumValid(min, min)); } + +TEST_F(SignedFourByteIntTest, SumOfHalfAndThreeFifthsMax) +{ + const int32_t threeFifths = (double)3 / 5 * max; + const int32_t half = (double)1 / 2 * max; + + EXPECT_FALSE(isSumValid(threeFifths, half)); +} + +TEST_F(SignedFourByteIntTest, SumOfTwoFifthsMax) +{ + const int32_t twoFifths = (double)2 / 5 * max; + + EXPECT_TRUE(isSumValid(twoFifths, twoFifths)); +} + +TEST_F(SignedFourByteIntTest, SumOfHalfMaxAndHalfMin) +{ + const int32_t halfMax = (double)1 / 2 * max; + const int32_t halfMin = (double)1 / 2 * min; + + EXPECT_TRUE(isSumValid(halfMax, halfMin)); +} + +TEST_F(SignedFourByteIntTest, SumOfMaxAndMin) { EXPECT_TRUE(isSumValid(max, min)); } + +class SignedEightByteIntTest : public ::testing::Test +{ + protected: + SignedEightByteIntTest() + : max(std::numeric_limits::max()), min(std::numeric_limits::min()) + { + } + + virtual ~SignedEightByteIntTest() {} + + virtual void SetUp() {} + + virtual void TearDown() {} + + const int64_t max; + const int64_t min; +}; + +TEST_F(SignedEightByteIntTest, SumOfMaxValues) { EXPECT_FALSE(isSumValid(max, max)); } + +TEST_F(SignedEightByteIntTest, SumOfMinValues) { EXPECT_FALSE(isSumValid(min, min)); } + +TEST_F(SignedEightByteIntTest, SumOfHalfAndThreeFifthsMax) +{ + const int64_t threeFifths = (double)3 / 5 * max; + const int64_t half = (double)1 / 2 * max; + + EXPECT_FALSE(isSumValid(threeFifths, half)); +} + +TEST_F(SignedEightByteIntTest, SumOfTwoFifthsMax) +{ + const int64_t twoFifths = (double)2 / 5 * max; + + EXPECT_TRUE(isSumValid(twoFifths, twoFifths)); +} + +TEST_F(SignedEightByteIntTest, SumOfHalfMaxAndHalfMin) +{ + const int64_t halfMax = (double)1 / 2 * max; + const int64_t halfMin = (double)1 / 2 * min; + + EXPECT_TRUE(isSumValid(halfMax, halfMin)); +} + +TEST_F(SignedEightByteIntTest, SumOfMaxAndMin) { EXPECT_TRUE(isSumValid(max, min)); } + +} // namespace \ No newline at end of file