From ba102c8389ede4f0f0c3663789e5f23ee413d391 Mon Sep 17 00:00:00 2001 From: Leonetienne Date: Mon, 15 Nov 2021 11:32:27 +0100 Subject: [PATCH] Moved Eule to its own repository --- .gitignore | 357 ++++ Eule.sln | 41 + Eule/Collider.cpp | 1 + Eule/Collider.h | 16 + Eule/Constants.h | 15 + Eule/Eule.vcxproj | 184 ++ Eule/Eule.vcxproj.filters | 75 + Eule/Math.cpp | 79 + Eule/Math.h | 101 ++ Eule/Matrix4x4.cpp | 649 +++++++ Eule/Matrix4x4.h | 145 ++ Eule/Quaternion.cpp | 336 ++++ Eule/Quaternion.h | 99 ++ Eule/Rect.h | 13 + Eule/TrapazoidalPrismCollider.cpp | 110 ++ Eule/TrapazoidalPrismCollider.h | 63 + Eule/Vector2.cpp | 700 ++++++++ Eule/Vector2.h | 103 ++ Eule/Vector3.cpp | 903 ++++++++++ Eule/Vector3.h | 111 ++ Eule/Vector4.cpp | 809 +++++++++ Eule/Vector4.h | 108 ++ Test/Math_Abs.cpp | 39 + Test/Math_Clamp.cpp | 87 + Test/Math_Lerp.cpp | 115 ++ Test/Math_Max.cpp | 40 + Test/Math_Min.cpp | 52 + Test/Math_Random.cpp | 26 + Test/Math_RandomIntRange.cpp | 100 ++ Test/Math_RandomInteger.cpp | 51 + Test/Math_Similar.cpp | 61 + Test/Math__Oscillate.cpp | 231 +++ Test/Math__RandomRange.cpp | 79 + Test/Matrix4x4.cpp | 984 +++++++++++ Test/Quaternion.cpp | 305 ++++ Test/TrapazoidalPrismCollider.cpp | 166 ++ Test/Vector2.cpp | 934 ++++++++++ Test/Vector3.cpp | 1553 +++++++++++++++++ Test/Vector4.cpp | 824 +++++++++ Test/VectorConversion.cpp | 220 +++ Test/_Test_Eule.vcxproj | 186 ++ Test/_Test_Eule.vcxproj.filters | 79 + _TestingUtilities/HandyMacros.h | 5 + _TestingUtilities/MemoryLeakDetector.h | 44 + _TestingUtilities/Testutil.h | 27 + _TestingUtilities/_MemoryLeakDetector.cpp | 105 ++ _TestingUtilities/_TestingUtilities.vcxproj | 168 ++ .../_TestingUtilities.vcxproj.filters | 29 + 48 files changed, 11528 insertions(+) create mode 100644 .gitignore create mode 100644 Eule.sln create mode 100644 Eule/Collider.cpp create mode 100644 Eule/Collider.h create mode 100644 Eule/Constants.h create mode 100644 Eule/Eule.vcxproj create mode 100644 Eule/Eule.vcxproj.filters create mode 100644 Eule/Math.cpp create mode 100644 Eule/Math.h create mode 100644 Eule/Matrix4x4.cpp create mode 100644 Eule/Matrix4x4.h create mode 100644 Eule/Quaternion.cpp create mode 100644 Eule/Quaternion.h create mode 100644 Eule/Rect.h create mode 100644 Eule/TrapazoidalPrismCollider.cpp create mode 100644 Eule/TrapazoidalPrismCollider.h create mode 100644 Eule/Vector2.cpp create mode 100644 Eule/Vector2.h create mode 100644 Eule/Vector3.cpp create mode 100644 Eule/Vector3.h create mode 100644 Eule/Vector4.cpp create mode 100644 Eule/Vector4.h create mode 100644 Test/Math_Abs.cpp create mode 100644 Test/Math_Clamp.cpp create mode 100644 Test/Math_Lerp.cpp create mode 100644 Test/Math_Max.cpp create mode 100644 Test/Math_Min.cpp create mode 100644 Test/Math_Random.cpp create mode 100644 Test/Math_RandomIntRange.cpp create mode 100644 Test/Math_RandomInteger.cpp create mode 100644 Test/Math_Similar.cpp create mode 100644 Test/Math__Oscillate.cpp create mode 100644 Test/Math__RandomRange.cpp create mode 100644 Test/Matrix4x4.cpp create mode 100644 Test/Quaternion.cpp create mode 100644 Test/TrapazoidalPrismCollider.cpp create mode 100644 Test/Vector2.cpp create mode 100644 Test/Vector3.cpp create mode 100644 Test/Vector4.cpp create mode 100644 Test/VectorConversion.cpp create mode 100644 Test/_Test_Eule.vcxproj create mode 100644 Test/_Test_Eule.vcxproj.filters create mode 100644 _TestingUtilities/HandyMacros.h create mode 100644 _TestingUtilities/MemoryLeakDetector.h create mode 100644 _TestingUtilities/Testutil.h create mode 100644 _TestingUtilities/_MemoryLeakDetector.cpp create mode 100644 _TestingUtilities/_TestingUtilities.vcxproj create mode 100644 _TestingUtilities/_TestingUtilities.vcxproj.filters diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b91a954 --- /dev/null +++ b/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +#infilename-tag +*_gitignore_* + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Visual Paradigm Shitfiles +*tornado.vpp.bak* +*tornado.*lck* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/Eule.sln b/Eule.sln new file mode 100644 index 0000000..59bc47c --- /dev/null +++ b/Eule.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Eule", "Eule\Eule.vcxproj", "{E15CD460-78CB-4B3F-BE85-C1E3205247B1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_Test_Eule", "Test\_Test_Eule.vcxproj", "{2B6C03E2-179C-45EA-9FDE-45D0214B4D5E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E15CD460-78CB-4B3F-BE85-C1E3205247B1}.Debug|x64.ActiveCfg = Debug|x64 + {E15CD460-78CB-4B3F-BE85-C1E3205247B1}.Debug|x64.Build.0 = Debug|x64 + {E15CD460-78CB-4B3F-BE85-C1E3205247B1}.Debug|x86.ActiveCfg = Debug|Win32 + {E15CD460-78CB-4B3F-BE85-C1E3205247B1}.Debug|x86.Build.0 = Debug|Win32 + {E15CD460-78CB-4B3F-BE85-C1E3205247B1}.Release|x64.ActiveCfg = Release|x64 + {E15CD460-78CB-4B3F-BE85-C1E3205247B1}.Release|x64.Build.0 = Release|x64 + {E15CD460-78CB-4B3F-BE85-C1E3205247B1}.Release|x86.ActiveCfg = Release|Win32 + {E15CD460-78CB-4B3F-BE85-C1E3205247B1}.Release|x86.Build.0 = Release|Win32 + {2B6C03E2-179C-45EA-9FDE-45D0214B4D5E}.Debug|x64.ActiveCfg = Debug|x64 + {2B6C03E2-179C-45EA-9FDE-45D0214B4D5E}.Debug|x64.Build.0 = Debug|x64 + {2B6C03E2-179C-45EA-9FDE-45D0214B4D5E}.Debug|x86.ActiveCfg = Debug|Win32 + {2B6C03E2-179C-45EA-9FDE-45D0214B4D5E}.Debug|x86.Build.0 = Debug|Win32 + {2B6C03E2-179C-45EA-9FDE-45D0214B4D5E}.Release|x64.ActiveCfg = Release|x64 + {2B6C03E2-179C-45EA-9FDE-45D0214B4D5E}.Release|x64.Build.0 = Release|x64 + {2B6C03E2-179C-45EA-9FDE-45D0214B4D5E}.Release|x86.ActiveCfg = Release|Win32 + {2B6C03E2-179C-45EA-9FDE-45D0214B4D5E}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6C9B2F5D-1E09-40DB-B373-74235CB824DD} + EndGlobalSection +EndGlobal diff --git a/Eule/Collider.cpp b/Eule/Collider.cpp new file mode 100644 index 0000000..38bcd04 --- /dev/null +++ b/Eule/Collider.cpp @@ -0,0 +1 @@ +#include "Collider.h" diff --git a/Eule/Collider.h b/Eule/Collider.h new file mode 100644 index 0000000..27cf6cd --- /dev/null +++ b/Eule/Collider.h @@ -0,0 +1,16 @@ +#pragma once +#include "Vector3.h" + +namespace Eule +{ + /** Abstract class of a collider domain. + * Specializations describe a shape in 3d space, and provide implementations of the methods below, + * for their specific shape. Examples could be a SphereCollider, a BoxCollider, etc... + */ + class Collider + { + public: + //! Tests, if this Collider contains a point + virtual bool Contains(const Vector3d& point) const = 0; + }; +} diff --git a/Eule/Constants.h b/Eule/Constants.h new file mode 100644 index 0000000..aab055b --- /dev/null +++ b/Eule/Constants.h @@ -0,0 +1,15 @@ +#pragma once + +// Pretty sure the compiler will optimize these calculations out... + +//! Pi up to 50 decimal places +#define PI 3.14159265358979323846264338327950288419716939937510 + +//! Pi divided by two +#define HALF_PI 1.57079632679489661923132169163975144209858469968755 + +//! Factor to convert degrees to radians +#define Deg2Rad 0.0174532925199432957692369076848861271344287188854172222222222222 + +//! Factor to convert radians to degrees +#define Rad2Deg 57.295779513082320876798154814105170332405472466564427711013084788 diff --git a/Eule/Eule.vcxproj b/Eule/Eule.vcxproj new file mode 100644 index 0000000..0505be7 --- /dev/null +++ b/Eule/Eule.vcxproj @@ -0,0 +1,184 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + 16.0 + Win32Proj + {e15cd460-78cb-4b3f-be85-c1e3205247b1} + Eule + 10.0 + + + + StaticLibrary + true + v142 + Unicode + + + StaticLibrary + false + v142 + true + Unicode + + + StaticLibrary + true + v142 + Unicode + + + StaticLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + pch.h + true + AdvancedVectorExtensions2 + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + pch.h + AdvancedVectorExtensions2 + + + + + true + true + true + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + pch.h + true + AdvancedVectorExtensions2 + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + pch.h + AdvancedVectorExtensions2 + + + + + true + true + true + + + + + + \ No newline at end of file diff --git a/Eule/Eule.vcxproj.filters b/Eule/Eule.vcxproj.filters new file mode 100644 index 0000000..dc84c03 --- /dev/null +++ b/Eule/Eule.vcxproj.filters @@ -0,0 +1,75 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + \ No newline at end of file diff --git a/Eule/Math.cpp b/Eule/Math.cpp new file mode 100644 index 0000000..6407ebe --- /dev/null +++ b/Eule/Math.cpp @@ -0,0 +1,79 @@ +#include "Math.h" +#include "Constants.h" +#include + +using namespace Eule; + +// Checks if the random number generator is initialized. Does nothing if it is, initializes if it isn't. +#define MAKE_SURE_RNG_IS_INITIALIZED if (!isRngInitialized) InitRng(); + +void Math::InitRng() +{ + // Create truly random source (from hardware events) + std::random_device randomSource; + + // Generate enough truly random values to populate the entire state of the mersenne twister + std::array seedValues; + std::generate_n(seedValues.data(), seedValues.size(), std::ref(randomSource)); + std::seed_seq seedSequence(seedValues.begin(), seedValues.end()); + + // Seed the mersenne twister with these values + rng = std::mt19937(seedSequence); + + isRngInitialized = true; + + return; +} + +// Will return a random double between 0 and 1 +double Math::Random() +{ + MAKE_SURE_RNG_IS_INITIALIZED; + + return (rng() % 694206942069ll) / 694206942069.0; +} + +// Will return a random unsigned integer. +unsigned int Math::RandomUint() +{ + MAKE_SURE_RNG_IS_INITIALIZED; + + return rng(); +} + +// Will return a random integer +unsigned int Math::RandomInt() +{ + MAKE_SURE_RNG_IS_INITIALIZED; + + // Since this is supposed to return a random value anyways, + // we can let the random uint overflow without any problems. + return (int)rng(); +} + +// Will return a random double within a range +// These bounds are INCLUSIVE! +double Math::RandomRange(double min, double max) +{ + return (Random() * (max - min)) + min; +} + +// Will return a random integer within a range. This is faster than '(int)RandomRange(x,y)' +// These bounds are INCLUSIVE! +int Math::RandomIntRange(int min, int max) +{ + return (rng() % (max + 1 - min)) + min; +} + +double Math::Oscillate(const double a, const double b, const double counter, const double speed) +{ + return (sin(counter * speed * PI - HALF_PI) * 0.5 + 0.5) * (b-a) + a; +} + +bool Math::RandomChance(const double chance) +{ + return Random() <= chance; +} + +std::mt19937 Math::rng; +bool Math::isRngInitialized = true; diff --git a/Eule/Math.h b/Eule/Math.h new file mode 100644 index 0000000..471ab40 --- /dev/null +++ b/Eule/Math.h @@ -0,0 +1,101 @@ +#pragma once +#include + +namespace Eule +{ + /** Math utility class. + */ + class Math + { + public: + //! Will return the bigger of two values + [[nodiscard]] static constexpr double Max(const double a, const double b); + + //! Will return the smaller of two values + [[nodiscard]] static constexpr double Min(const double a, const double b); + + //! Will return `v`, but at least `min`, and at most `max` + [[nodiscard]] static constexpr double Clamp(const double v, const double min, const double max); + + //! Will return the linear interpolation between `a` and `b` by `t` + [[nodiscard]] static constexpr double Lerp(double a, double b, double t); + + //! Will return the absolute value of `a` + [[nodiscard]] static constexpr double Abs(const double a); + + //! Compares two double values with a given accuracy + [[nodiscard]] static constexpr bool Similar(const double a, const double b, const double epsilon = 0.00001); + + //! Will return a random double between `0` and `1` + static double Random(); + + //! Will return a random unsigned integer. + static unsigned int RandomUint(); + + //! Will return a random integer + static unsigned int RandomInt(); + + //! Will return a random double within a range + //! These bounds are INCLUSIVE! + static double RandomRange(const double min, const double max); + + //! Will return a random integer within a range. This is faster than `(int)RandomRange(x,y)` + //! These bounds are INCLUSIVE! + static int RandomIntRange(const int max, const int min); + + //! Will 'roll' a dice, returning `true` \f$100 * chance\f$ percent of the time. + static bool RandomChance(const double chance); + + //! Kind of like \f$sin(counter)\f$, but it oscillates over \f$[a,b]\f$ instead of \f$[-1,1]\f$, by a given speed. + //! Given that \f$speed = 1\f$, the result will always be `a` if `counter` is even, and `b` if `counter` is uneven. + //! If `counter` is a rational, the result will oscillate between `a` and `b`, like `sin()` does. + //! If you increase `speed`, the oscillation frequency will increase. Meaning \f$speed = 2\f$ would result in \f$counter=0.5\f$ returning `b`. + static double Oscillate(const double a, const double b, const double counter, const double speed); + + private: + //! Will initialize the random number generator + static void InitRng(); + + static std::mt19937 rng; + static bool isRngInitialized; + + // No instanciation! >:( + Math(); + }; + + + + /* These are just the inline methods. They have to lie in the header file. */ + /* The more sophisticated methods are in the .cpp */ + + constexpr inline double Math::Max(double a, double b) + { + return (a > b) ? a : b; + } + + constexpr inline double Math::Min(double a, double b) + { + return (a < b) ? a : b; + } + + constexpr inline double Math::Clamp(double v, double min, double max) + { + return Max(Min(v, max), min); + } + + constexpr inline double Math::Lerp(double a, double b, double t) + { + const double it = 1.0 - t; + return (a * it) + (b * t); + } + + inline constexpr double Math::Abs(const double a) + { + return (a > 0.0) ? a : -a; + } + + inline constexpr bool Math::Math::Similar(const double a, const double b, const double epsilon) + { + return Abs(a - b) <= epsilon; + } +} diff --git a/Eule/Matrix4x4.cpp b/Eule/Matrix4x4.cpp new file mode 100644 index 0000000..c8bb58a --- /dev/null +++ b/Eule/Matrix4x4.cpp @@ -0,0 +1,649 @@ +#include "Matrix4x4.h" +#include "Vector3.h" +#include "Math.h" + +//#define _EULE_NO_INTRINSICS_ +#ifndef _EULE_NO_INTRINSICS_ +#include +#endif + +using namespace Eule; + +Matrix4x4::Matrix4x4() +{ + // Create identity matrix + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + v[i][j] = double(i == j); + + return; +} + +Matrix4x4::Matrix4x4(const Matrix4x4& other) +{ + v = other.v; + return; +} + +Matrix4x4::Matrix4x4(Matrix4x4&& other) noexcept +{ + v = std::move(other.v); + return; +} + +Matrix4x4 Matrix4x4::operator*(const Matrix4x4& other) const +{ + Matrix4x4 newMatrix; + newMatrix.p = 1; + + #ifndef _EULE_NO_INTRINSICS_ + + + /* <= Matrix3x3 multiplication => */ + + // Load matrix components + __m256d __va1 = _mm256_set_pd(v[0][0], v[0][0], v[0][0], v[1][0]); + __m256d __va2 = _mm256_set_pd(v[1][0], v[1][0], v[2][0], v[2][0]); + + __m256d __oa1 = _mm256_set_pd(other[0][0], other[0][1], other[0][2], other[0][0]); + __m256d __oa2 = _mm256_set_pd(other[0][1], other[0][2], other[0][0], other[0][1]); + + __m256d __vb1 = _mm256_set_pd(v[0][1], v[0][1], v[0][1], v[1][1]); + __m256d __vb2 = _mm256_set_pd(v[1][1], v[1][1], v[2][1], v[2][1]); + + __m256d __ob1 = _mm256_set_pd(other[1][0], other[1][1], other[1][2], other[1][0]); + __m256d __ob2 = _mm256_set_pd(other[1][1], other[1][2], other[1][0], other[1][1]); + + __m256d __vc1 = _mm256_set_pd(v[0][2], v[0][2], v[0][2], v[1][2]); + __m256d __vc2 = _mm256_set_pd(v[1][2], v[1][2], v[2][2], v[2][2]); + + __m256d __oc1 = _mm256_set_pd(other[2][0], other[2][1], other[2][2], other[2][0]); + __m256d __oc2 = _mm256_set_pd(other[2][1], other[2][2], other[2][0], other[2][1]); + + // Initialize sums + __m256d __sum1 = _mm256_set1_pd(0); + __m256d __sum2 = _mm256_set1_pd(0); + + // Let's multiply-add them together + // First, the first block + __sum1 = _mm256_fmadd_pd(__va1, __oa1, __sum1); + __sum1 = _mm256_fmadd_pd(__vb1, __ob1, __sum1); + __sum1 = _mm256_fmadd_pd(__vc1, __oc1, __sum1); + + // Then the second block + __sum2 = _mm256_fmadd_pd(__va2, __oa2, __sum2); + __sum2 = _mm256_fmadd_pd(__vb2, __ob2, __sum2); + __sum2 = _mm256_fmadd_pd(__vc2, __oc2, __sum2); + + // Retrieve results + double sum1[4]; + double sum2[4]; + + _mm256_storeu_pd(sum1, __sum1); + _mm256_storeu_pd(sum2, __sum2); + + // Apply results + // Block 1 + newMatrix[0][0] = sum1[3]; + newMatrix[0][1] = sum1[2]; + newMatrix[0][2] = sum1[1]; + newMatrix[1][0] = sum1[0]; + + // Block 2 + newMatrix[1][1] = sum2[3]; + newMatrix[1][2] = sum2[2]; + newMatrix[2][0] = sum2[1]; + newMatrix[2][1] = sum2[0]; + + // Does not fit in the intrinsic calculation. Might just calculate 'by hand'. + newMatrix[2][2] = (v[2][0] * other[0][2]) + (v[2][1] * other[1][2]) + (v[2][2] * other[2][2]); + + + /* <= Translation component => */ + + // Load translation components into registers + __m256d __transSelf = _mm256_set_pd(0, l, h, d); + __m256d __transOther = _mm256_set_pd(0, other.l, other.h, other.d); + + // Let's add them + __m256d __sum = _mm256_add_pd(__transSelf, __transOther); + + // Retrieve results + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + // Apply them + newMatrix.d = sum[0]; + newMatrix.h = sum[1]; + newMatrix.l = sum[2]; + + #else + + + // Rotation, Scaling + newMatrix[0][0] = (v[0][0] * other[0][0]) + (v[0][1] * other[1][0]) + (v[0][2] * other[2][0]); + newMatrix[0][1] = (v[0][0] * other[0][1]) + (v[0][1] * other[1][1]) + (v[0][2] * other[2][1]); + newMatrix[0][2] = (v[0][0] * other[0][2]) + (v[0][1] * other[1][2]) + (v[0][2] * other[2][2]); + + newMatrix[1][0] = (v[1][0] * other[0][0]) + (v[1][1] * other[1][0]) + (v[1][2] * other[2][0]); + newMatrix[1][1] = (v[1][0] * other[0][1]) + (v[1][1] * other[1][1]) + (v[1][2] * other[2][1]); + newMatrix[1][2] = (v[1][0] * other[0][2]) + (v[1][1] * other[1][2]) + (v[1][2] * other[2][2]); + + newMatrix[2][0] = (v[2][0] * other[0][0]) + (v[2][1] * other[1][0]) + (v[2][2] * other[2][0]); + newMatrix[2][1] = (v[2][0] * other[0][1]) + (v[2][1] * other[1][1]) + (v[2][2] * other[2][1]); + newMatrix[2][2] = (v[2][0] * other[0][2]) + (v[2][1] * other[1][2]) + (v[2][2] * other[2][2]); + + + // Translation + newMatrix[0][3] = v[0][3] + other[0][3]; + newMatrix[1][3] = v[1][3] + other[1][3]; + newMatrix[2][3] = v[2][3] + other[2][3]; + + #endif + + return newMatrix; +} + +void Matrix4x4::operator*=(const Matrix4x4& other) +{ + *this = *this * other; + return; +} + +Matrix4x4 Matrix4x4::operator/(const Matrix4x4& other) const +{ + return *this * other.Inverse3x3(); +} + +void Matrix4x4::operator/=(const Matrix4x4& other) +{ + *this = *this * other.Inverse3x3(); + return; +} + +Matrix4x4 Matrix4x4::operator*(const double scalar) const +{ + Matrix4x4 m; + + #ifndef _EULE_NO_INTRINSICS_ + + // Load matrix rows + __m256d __row0 = _mm256_set_pd(v[0][3], v[0][2], v[0][1], v[0][0]); + __m256d __row1 = _mm256_set_pd(v[1][3], v[1][2], v[1][1], v[1][0]); + __m256d __row2 = _mm256_set_pd(v[2][3], v[2][2], v[2][1], v[2][0]); + __m256d __row3 = _mm256_set_pd(v[3][3], v[3][2], v[3][1], v[3][0]); + + // Load scalar + __m256d __scalar = _mm256_set1_pd(scalar); + + // Scale values + __m256d __sr0 = _mm256_mul_pd(__row0, __scalar); + __m256d __sr1 = _mm256_mul_pd(__row1, __scalar); + __m256d __sr2 = _mm256_mul_pd(__row2, __scalar); + __m256d __sr3 = _mm256_mul_pd(__row3, __scalar); + + // Extract results + _mm256_storeu_pd(m.v[0].data(), __sr0); + _mm256_storeu_pd(m.v[1].data(), __sr1); + _mm256_storeu_pd(m.v[2].data(), __sr2); + _mm256_storeu_pd(m.v[3].data(), __sr3); + + #else + + for (std::size_t x = 0; x < 4; x++) + for (std::size_t y = 0; y < 4; y++) + m[x][y] = v[x][y] * scalar; + + #endif + + return m; +} + +void Matrix4x4::operator*=(const double scalar) +{ + *this = *this * scalar; + return; +} + +Matrix4x4 Matrix4x4::operator/(const double denominator) const +{ + const double precomputeDivision = 1.0 / denominator; + + return *this * precomputeDivision; +} + +void Matrix4x4::operator/=(const double denominator) +{ + *this = *this / denominator; + return; +} + +Matrix4x4 Matrix4x4::operator+(const Matrix4x4& other) const +{ + Matrix4x4 m; + + #ifndef _EULE_NO_INTRINSICS_ + + // Load matrix rows + __m256d __row0a = _mm256_set_pd(v[0][3], v[0][2], v[0][1], v[0][0]); + __m256d __row1a = _mm256_set_pd(v[1][3], v[1][2], v[1][1], v[1][0]); + __m256d __row2a = _mm256_set_pd(v[2][3], v[2][2], v[2][1], v[2][0]); + __m256d __row3a = _mm256_set_pd(v[3][3], v[3][2], v[3][1], v[3][0]); + + __m256d __row0b = _mm256_set_pd(other[0][3], other[0][2], other[0][1], other[0][0]); + __m256d __row1b = _mm256_set_pd(other[1][3], other[1][2], other[1][1], other[1][0]); + __m256d __row2b = _mm256_set_pd(other[2][3], other[2][2], other[2][1], other[2][0]); + __m256d __row3b = _mm256_set_pd(other[3][3], other[3][2], other[3][1], other[3][0]); + + // Add rows + __m256d __sr0 = _mm256_add_pd(__row0a, __row0b); + __m256d __sr1 = _mm256_add_pd(__row1a, __row1b); + __m256d __sr2 = _mm256_add_pd(__row2a, __row2b); + __m256d __sr3 = _mm256_add_pd(__row3a, __row3b); + + // Extract results + _mm256_storeu_pd(m.v[0].data(), __sr0); + _mm256_storeu_pd(m.v[1].data(), __sr1); + _mm256_storeu_pd(m.v[2].data(), __sr2); + _mm256_storeu_pd(m.v[3].data(), __sr3); + + #else + + for (std::size_t x = 0; x < 4; x++) + for (std::size_t y = 0; y < 4; y++) + m[x][y] = v[x][y] + other[x][y]; + + #endif + + return m; +} + +void Matrix4x4::operator+=(const Matrix4x4& other) +{ + #ifndef _EULE_NO_INTRINSICS_ + // Doing it again is a tad directer, and thus faster. We avoid an intermittent Matrix4x4 instance + + // Load matrix rows + __m256d __row0a = _mm256_set_pd(v[0][3], v[0][2], v[0][1], v[0][0]); + __m256d __row1a = _mm256_set_pd(v[1][3], v[1][2], v[1][1], v[1][0]); + __m256d __row2a = _mm256_set_pd(v[2][3], v[2][2], v[2][1], v[2][0]); + __m256d __row3a = _mm256_set_pd(v[3][3], v[3][2], v[3][1], v[3][0]); + + __m256d __row0b = _mm256_set_pd(other[0][3], other[0][2], other[0][1], other[0][0]); + __m256d __row1b = _mm256_set_pd(other[1][3], other[1][2], other[1][1], other[1][0]); + __m256d __row2b = _mm256_set_pd(other[2][3], other[2][2], other[2][1], other[2][0]); + __m256d __row3b = _mm256_set_pd(other[3][3], other[3][2], other[3][1], other[3][0]); + + // Add rows + __m256d __sr0 = _mm256_add_pd(__row0a, __row0b); + __m256d __sr1 = _mm256_add_pd(__row1a, __row1b); + __m256d __sr2 = _mm256_add_pd(__row2a, __row2b); + __m256d __sr3 = _mm256_add_pd(__row3a, __row3b); + + // Extract results + _mm256_storeu_pd(v[0].data(), __sr0); + _mm256_storeu_pd(v[1].data(), __sr1); + _mm256_storeu_pd(v[2].data(), __sr2); + _mm256_storeu_pd(v[3].data(), __sr3); + + #else + + *this = *this + other; + + #endif + + return; +} + +Matrix4x4 Matrix4x4::operator-(const Matrix4x4& other) const +{ + Matrix4x4 m; + + #ifndef _EULE_NO_INTRINSICS_ + + // Load matrix rows + __m256d __row0a = _mm256_set_pd(v[0][3], v[0][2], v[0][1], v[0][0]); + __m256d __row1a = _mm256_set_pd(v[1][3], v[1][2], v[1][1], v[1][0]); + __m256d __row2a = _mm256_set_pd(v[2][3], v[2][2], v[2][1], v[2][0]); + __m256d __row3a = _mm256_set_pd(v[3][3], v[3][2], v[3][1], v[3][0]); + + __m256d __row0b = _mm256_set_pd(other[0][3], other[0][2], other[0][1], other[0][0]); + __m256d __row1b = _mm256_set_pd(other[1][3], other[1][2], other[1][1], other[1][0]); + __m256d __row2b = _mm256_set_pd(other[2][3], other[2][2], other[2][1], other[2][0]); + __m256d __row3b = _mm256_set_pd(other[3][3], other[3][2], other[3][1], other[3][0]); + + // Subtract rows + __m256d __sr0 = _mm256_sub_pd(__row0a, __row0b); + __m256d __sr1 = _mm256_sub_pd(__row1a, __row1b); + __m256d __sr2 = _mm256_sub_pd(__row2a, __row2b); + __m256d __sr3 = _mm256_sub_pd(__row3a, __row3b); + + // Extract results + _mm256_storeu_pd(m.v[0].data(), __sr0); + _mm256_storeu_pd(m.v[1].data(), __sr1); + _mm256_storeu_pd(m.v[2].data(), __sr2); + _mm256_storeu_pd(m.v[3].data(), __sr3); + + #else + + for (std::size_t x = 0; x < 4; x++) + for (std::size_t y = 0; y < 4; y++) + m[x][y] = v[x][y] - other[x][y]; + + #endif + + return m; +} + +void Matrix4x4::operator-=(const Matrix4x4& other) +{ + #ifndef _EULE_NO_INTRINSICS_ + // Doing it again is a tad directer, and thus faster. We avoid an intermittent Matrix4x4 instance + + // Load matrix rows + __m256d __row0a = _mm256_set_pd(v[0][3], v[0][2], v[0][1], v[0][0]); + __m256d __row1a = _mm256_set_pd(v[1][3], v[1][2], v[1][1], v[1][0]); + __m256d __row2a = _mm256_set_pd(v[2][3], v[2][2], v[2][1], v[2][0]); + __m256d __row3a = _mm256_set_pd(v[3][3], v[3][2], v[3][1], v[3][0]); + + __m256d __row0b = _mm256_set_pd(other[0][3], other[0][2], other[0][1], other[0][0]); + __m256d __row1b = _mm256_set_pd(other[1][3], other[1][2], other[1][1], other[1][0]); + __m256d __row2b = _mm256_set_pd(other[2][3], other[2][2], other[2][1], other[2][0]); + __m256d __row3b = _mm256_set_pd(other[3][3], other[3][2], other[3][1], other[3][0]); + + // Subtract rows + __m256d __sr0 = _mm256_sub_pd(__row0a, __row0b); + __m256d __sr1 = _mm256_sub_pd(__row1a, __row1b); + __m256d __sr2 = _mm256_sub_pd(__row2a, __row2b); + __m256d __sr3 = _mm256_sub_pd(__row3a, __row3b); + + // Extract results + _mm256_storeu_pd(v[0].data(), __sr0); + _mm256_storeu_pd(v[1].data(), __sr1); + _mm256_storeu_pd(v[2].data(), __sr2); + _mm256_storeu_pd(v[3].data(), __sr3); + + #else + + * this = *this - other; + + #endif + + return; +} + +std::array& Matrix4x4::operator[](std::size_t y) +{ + return v[y]; +} + +const std::array& Matrix4x4::operator[](std::size_t y) const +{ + return v[y]; +} + +void Matrix4x4::operator=(const Matrix4x4& other) +{ + v = other.v; + return; +} + +void Matrix4x4::operator=(Matrix4x4&& other) noexcept +{ + v = std::move(other.v); + return; +} + +bool Matrix4x4::operator==(const Matrix4x4& other) +{ + return v == other.v; +} + +bool Matrix4x4::operator!=(const Matrix4x4& other) +{ + return !operator==(other); +} + +const Vector3d Matrix4x4::GetTranslationComponent() const +{ + return Vector3d(d, h, l); +} + +void Matrix4x4::SetTranslationComponent(const Vector3d& trans) +{ + d = trans.x; + h = trans.y; + l = trans.z; + return; +} + +Matrix4x4 Matrix4x4::DropTranslationComponents() const +{ + Matrix4x4 m(*this); + m.d = 0; + m.h = 0; + m.l = 0; + return m; +} + +Matrix4x4 Matrix4x4::Transpose3x3() const +{ + Matrix4x4 trans(*this); // Keep other cells + + for (std::size_t i = 0; i < 3; i++) + for (std::size_t j = 0; j < 3; j++) + trans[j][i] = v[i][j]; + + return trans; +} + +Matrix4x4 Matrix4x4::Transpose4x4() const +{ + Matrix4x4 trans; + + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + trans[j][i] = v[i][j]; + + return trans; +} + +Matrix4x4 Matrix4x4::Multiply4x4(const Matrix4x4& o) const +{ + Matrix4x4 m; + + m[0][0] = (v[0][0]*o[0][0]) + (v[0][1]*o[1][0]) + (v[0][2]*o[2][0]) + (v[0][3]*o[3][0]); + m[0][1] = (v[0][0]*o[0][1]) + (v[0][1]*o[1][1]) + (v[0][2]*o[2][1]) + (v[0][3]*o[3][1]); + m[0][2] = (v[0][0]*o[0][2]) + (v[0][1]*o[1][2]) + (v[0][2]*o[2][2]) + (v[0][3]*o[3][2]); + m[0][3] = (v[0][0]*o[0][3]) + (v[0][1]*o[1][3]) + (v[0][2]*o[2][3]) + (v[0][3]*o[3][3]); + + m[1][0] = (v[1][0]*o[0][0]) + (v[1][1]*o[1][0]) + (v[1][2]*o[2][0]) + (v[1][3]*o[3][0]); + m[1][1] = (v[1][0]*o[0][1]) + (v[1][1]*o[1][1]) + (v[1][2]*o[2][1]) + (v[1][3]*o[3][1]); + m[1][2] = (v[1][0]*o[0][2]) + (v[1][1]*o[1][2]) + (v[1][2]*o[2][2]) + (v[1][3]*o[3][2]); + m[1][3] = (v[1][0]*o[0][3]) + (v[1][1]*o[1][3]) + (v[1][2]*o[2][3]) + (v[1][3]*o[3][3]); + + m[2][0] = (v[2][0]*o[0][0]) + (v[2][1]*o[1][0]) + (v[2][2]*o[2][0]) + (v[2][3]*o[3][0]); + m[2][1] = (v[2][0]*o[0][1]) + (v[2][1]*o[1][1]) + (v[2][2]*o[2][1]) + (v[2][3]*o[3][1]); + m[2][2] = (v[2][0]*o[0][2]) + (v[2][1]*o[1][2]) + (v[2][2]*o[2][2]) + (v[2][3]*o[3][2]); + m[2][3] = (v[2][0]*o[0][3]) + (v[2][1]*o[1][3]) + (v[2][2]*o[2][3]) + (v[2][3]*o[3][3]); + + m[3][0] = (v[3][0]*o[0][0]) + (v[3][1]*o[1][0]) + (v[3][2]*o[2][0]) + (v[3][3]*o[3][0]); + m[3][1] = (v[3][0]*o[0][1]) + (v[3][1]*o[1][1]) + (v[3][2]*o[2][1]) + (v[3][3]*o[3][1]); + m[3][2] = (v[3][0]*o[0][2]) + (v[3][1]*o[1][2]) + (v[3][2]*o[2][2]) + (v[3][3]*o[3][2]); + m[3][3] = (v[3][0]*o[0][3]) + (v[3][1]*o[1][3]) + (v[3][2]*o[2][3]) + (v[3][3]*o[3][3]); + + return m; +} + +Matrix4x4 Matrix4x4::GetCofactors(std::size_t p, std::size_t q, std::size_t n) const +{ + if (n > 4) + throw std::runtime_error("Dimension out of range! 0 <= n <= 4"); + + Matrix4x4 cofs; + + std::size_t i = 0; + std::size_t j = 0; + + for (std::size_t y = 0; y < n; y++) + for (std::size_t x = 0; x < n; x++) + { + if ((y != p) && (x != q)) + { + cofs[i][j] = v[y][x]; + j++; + } + + if (j == n - 1) + { + j = 0; + i++; + } + } + + return cofs; +} + +/* +* BEGIN_REF +* https://www.geeksforgeeks.org/adjoint-inverse-matrix/ +*/ +double Matrix4x4::Determinant(std::size_t n) const +{ + if (n > 4) + throw std::runtime_error("Dimension out of range! 0 <= n <= 4"); + + double d = 0; + double sign = 1; + + if (n == 1) + return v[0][0]; + + for (std::size_t x = 0; x < n; x++) + { + Matrix4x4 cofs = GetCofactors(0, x, n); + + d += sign * v[0][x] * cofs.Determinant(n - 1); + sign = -sign; + } + + return d; +} + +Matrix4x4 Matrix4x4::Adjoint(std::size_t n) const +{ + if (n > 4) + throw std::runtime_error("Dimension out of range! 0 <= n <= 4"); + + Matrix4x4 adj; + double sign = 1; + + for (std::size_t i = 0; i < n; i++) + for (std::size_t j = 0; j < n; j++) + { + Matrix4x4 cofs = GetCofactors(i, j, n); + + // sign of adj[j][i] positive if sum of row + // and column indexes is even. + sign = ((i + j) % 2 == 0) ? 1 : -1; + + // Interchanging rows and columns to get the + // transpose of the cofactor matrix + adj[j][i] = sign * (cofs.Determinant(n - 1)); + } + + return adj; +} + +Matrix4x4 Matrix4x4::Inverse3x3() const +{ + Matrix4x4 inv; + + double det = Determinant(3); + if (det == 0.0) + throw std::runtime_error("Matrix3x3 not inversible!"); + + Matrix4x4 adj = Adjoint(3); + + for (std::size_t i = 0; i < 3; i++) + for (std::size_t j = 0; j < 3; j++) + inv[i][j] = adj[i][j] / det; + + inv.SetTranslationComponent(-GetTranslationComponent()); + + return inv; +} + +Matrix4x4 Matrix4x4::Inverse4x4() const +{ + Matrix4x4 inv; + + double det = Determinant(4); + if (det == 0.0) + throw std::runtime_error("Matrix4x4 not inversible!"); + + Matrix4x4 adj = Adjoint(4); + + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + inv[i][j] = adj[i][j] / det; + + return inv; +} + +/* +* END REF +*/ + +bool Matrix4x4::IsInversible3x3() const +{ + return (Determinant(3) != 0); +} + +bool Matrix4x4::IsInversible4x4() const +{ + return (Determinant(4) != 0); +} + +bool Matrix4x4::Similar(const Matrix4x4& other, double epsilon) const +{ + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + if (!Math::Similar(v[i][j], other[i][j], epsilon)) + return false; + + return true; +} + +namespace Eule +{ + std::ostream& operator<<(std::ostream& os, const Matrix4x4& m) + { + os << std::endl; + + for (std::size_t y = 0; y < 4; y++) + { + for (std::size_t x = 0; x < 4; x++) + os << " | " << m[y][x]; + + os << " |" << std::endl; + } + + return os; + } + + std::wostream& operator<<(std::wostream& os, const Matrix4x4& m) + { + os << std::endl; + + for (std::size_t y = 0; y < 4; y++) + { + for (std::size_t x = 0; x < 4; x++) + os << L" | " << m[y][x]; + + os << L" |" << std::endl; + } + + return os; + } +} diff --git a/Eule/Matrix4x4.h b/Eule/Matrix4x4.h new file mode 100644 index 0000000..1a94fbf --- /dev/null +++ b/Eule/Matrix4x4.h @@ -0,0 +1,145 @@ +#pragma once +#include +#include +#include + +namespace Eule +{ + template + class Vector3; + typedef Vector3 Vector3d; + + /** A matrix 4x4 class representing a 3d transformation. + * This matrix consists of a 3x3 matrix containing scaling and rotation information, and a vector (d,h,l) + * representing the translation. + * + * ``` + * myMatrix[y][x] = 3 + * + * X ==============> + * Y + * | # # # # # # # # # # # + * | # a b c d # + * | # # + * | # e f g h # + * | # # + * V # i j k l # + * # # + * # m n o p # + * # # # # # # # # # # # + * + * ``` + * + * Note: This class can also be used to compute regular 4x4 multiplications. Use Multiply4x4() for that. + */ + + class Matrix4x4 + { + public: + Matrix4x4(); + Matrix4x4(const Matrix4x4& other); + Matrix4x4(Matrix4x4&& other) noexcept; + + //! Array holding the matrices values + std::array, 4> v; + + Matrix4x4 operator*(const Matrix4x4& other) const; + void operator*=(const Matrix4x4& other); + + Matrix4x4 operator/(const Matrix4x4& other) const; + void operator/=(const Matrix4x4& other); + + //! Cellwise scaling + Matrix4x4 operator*(const double scalar) const; + //! Cellwise scaling + void operator*=(const double scalar); + + //! Cellwise division + Matrix4x4 operator/(const double denominator) const; + //! Cellwise division + void operator/=(const double denominator); + + //! Cellwise addition + Matrix4x4 operator+(const Matrix4x4& other) const; + //! Cellwise addition + void operator+=(const Matrix4x4& other); + + //! Cellwise subtraction + Matrix4x4 operator-(const Matrix4x4& other) const; + //! Cellwise subtraction + void operator-=(const Matrix4x4& other); + + + std::array& operator[](std::size_t y); + const std::array& operator[](std::size_t y) const; + + void operator=(const Matrix4x4& other); + void operator=(Matrix4x4&& other) noexcept; + + bool operator==(const Matrix4x4& other); + bool operator!=(const Matrix4x4& other); + + //! Will return d,h,l as a Vector3d(x,y,z) + const Vector3d GetTranslationComponent() const; + //! Will set d,h,l from a Vector3d(x,y,z) + void SetTranslationComponent(const Vector3d& trans); + + //! Will return this Matrix4x4 with d,h,l being set to 0 + Matrix4x4 DropTranslationComponents() const; + + //! Will return the 3x3 transpose of this matrix + Matrix4x4 Transpose3x3() const; + + //! Will return the 4x4 transpose of this matrix + Matrix4x4 Transpose4x4() const; + + //! Will return the Matrix4x4 of an actual 4x4 multiplication. operator* only does a 3x3 + Matrix4x4 Multiply4x4(const Matrix4x4& o) const; + + //! Will return the cofactors of this matrix, by dimension n + Matrix4x4 GetCofactors(std::size_t p, std::size_t q, std::size_t n) const; + + //! Will return the determinant, by dimension n + double Determinant(std::size_t n) const; + + //! Will return the adjoint of this matrix, by dimension n + Matrix4x4 Adjoint(std::size_t n) const; + + //! Will return the 3x3-inverse of this matrix. + //! Meaning, the 3x3 component will be inverted, and the translation component will be negated + Matrix4x4 Inverse3x3() const; + + //! Will return the full 4x4-inverse of this matrix + Matrix4x4 Inverse4x4() const; + + //! Will check if the 3x3-component is inversible + bool IsInversible3x3() const; + + //! Will check if the entire matrix is inversible + bool IsInversible4x4() const; + + //! Will compare if two matrices are similar to a certain epsilon value + bool Similar(const Matrix4x4& other, double epsilon = 0.00001) const; + + friend std::ostream& operator<<(std::ostream& os, const Matrix4x4& m); + friend std::wostream& operator<<(std::wostream& os, const Matrix4x4& m); + + // Shorthands + double& a = v[0][0]; + double& b = v[0][1]; + double& c = v[0][2]; + double& d = v[0][3]; + double& e = v[1][0]; + double& f = v[1][1]; + double& g = v[1][2]; + double& h = v[1][3]; + double& i = v[2][0]; + double& j = v[2][1]; + double& k = v[2][2]; + double& l = v[2][3]; + double& m = v[3][0]; + double& n = v[3][1]; + double& o = v[3][2]; + double& p = v[3][3]; + }; +} diff --git a/Eule/Quaternion.cpp b/Eule/Quaternion.cpp new file mode 100644 index 0000000..56dda36 --- /dev/null +++ b/Eule/Quaternion.cpp @@ -0,0 +1,336 @@ +#include "Quaternion.h" +#include "Constants.h" + +//#define _EULE_NO_INTRINSICS_ +#ifndef _EULE_NO_INTRINSICS_ +#include +#endif + +using namespace Eule; + +Quaternion::Quaternion() +{ + v = Vector4d(0, 0, 0, 1); + return; +} + +Quaternion::Quaternion(const Vector4d values) +{ + v = values; + return; +} + +Quaternion::Quaternion(const Quaternion& q) +{ + v = q.v; + return; +} + +Quaternion::Quaternion(const Vector3d eulerAngles) +{ + Vector3d eulerRad = eulerAngles * Deg2Rad; + + #ifndef _EULE_NO_INTRINSICS_ + + // Calculate sine and cos values + __m256d __vec = _mm256_set_pd(0, eulerRad.z, eulerRad.y, eulerRad.x); + __vec = _mm256_mul_pd(__vec, _mm256_set1_pd(0.5)); + __m256d __cos; + __m256d __sin = _mm256_sincos_pd(&__cos, __vec); + + // Create multiplication vectors + double sin[4]; + double cos[4]; + + _mm256_storeu_pd(sin, __sin); + _mm256_storeu_pd(cos, __cos); + + __m256d __a = _mm256_set_pd(cos[0], cos[0], sin[0], cos[0]); + __m256d __b = _mm256_set_pd(cos[1], sin[1], cos[1], cos[1]); + __m256d __c = _mm256_set_pd(sin[2], cos[2], cos[2], cos[2]); + + __m256d __d = _mm256_set_pd(sin[0], sin[0], cos[0], sin[0]); + __m256d __e = _mm256_set_pd(sin[1], cos[1], sin[1], sin[1]); + __m256d __f = _mm256_set_pd(cos[2], sin[2], sin[2], sin[2]); + + // Multiply them + __m256d __abc; + __abc = _mm256_mul_pd(__a, __b); + __abc = _mm256_mul_pd(__abc, __c); + + __m256d __def; + __def = _mm256_mul_pd(__d, __e); + __def = _mm256_mul_pd(__def, __f); + + // Extract results + double abc[4]; + double def[4]; + + _mm256_storeu_pd(abc, __abc); + _mm256_storeu_pd(def, __def); + + // Sum them up + v.w = abc[0] + def[0]; + v.x = abc[1] - def[1]; + v.y = abc[2] + def[2]; + v.z = abc[3] - def[3]; + + #else + + const double cy = cos(eulerRad.z * 0.5); + const double sy = sin(eulerRad.z * 0.5); + const double cp = cos(eulerRad.y * 0.5); + const double sp = sin(eulerRad.y * 0.5); + const double cr = cos(eulerRad.x * 0.5); + const double sr = sin(eulerRad.x * 0.5); + + v.w = cr * cp * cy + sr * sp * sy; + v.x = sr * cp * cy - cr * sp * sy; + v.y = cr * sp * cy + sr * cp * sy; + v.z = cr * cp * sy - sr * sp * cy; + + #endif + + return; +} + +Quaternion::~Quaternion() +{ + return; +} + +Quaternion Quaternion::operator= (const Quaternion& q) +{ + InvalidateCache(); + + v = q.v; + + return (*this); +} + +Quaternion Quaternion::operator* (const Quaternion& q) const +{ + return Quaternion(Vector4d( + v.w * q.v.x + v.x * q.v.w + v.y * q.v.z - v.z * q.v.y, + v.w * q.v.y + v.y * q.v.w + v.z * q.v.x - v.x * q.v.z, + v.w * q.v.z + v.z * q.v.w + v.x * q.v.y - v.y * q.v.x, + v.w * q.v.w - v.x * q.v.x - v.y * q.v.y - v.z * q.v.z + )); +} + +Quaternion Quaternion::operator*(const double scale) const +{ + return Quaternion(v * scale); +} + +Quaternion Quaternion::operator/ (Quaternion& q) const +{ + return ((*this) * (q.Inverse())); +} + +Quaternion& Quaternion::operator*= (const Quaternion& q) +{ + InvalidateCache(); + + Vector4d bufr = v; + v.x = bufr.w * q.v.x + bufr.x * q.v.w + bufr.y * q.v.z - bufr.z * q.v.y; // x + v.y = bufr.w * q.v.y + bufr.y * q.v.w + bufr.z * q.v.x - bufr.x * q.v.z; // y + v.z = bufr.w * q.v.z + bufr.z * q.v.w + bufr.x * q.v.y - bufr.y * q.v.x; // z + v.w = bufr.w * q.v.w - bufr.x * q.v.x - bufr.y * q.v.y - bufr.z * q.v.z; // w + + return (*this); +} + +Quaternion& Quaternion::operator*=(const double scale) +{ + InvalidateCache(); + + v *= scale; + return (*this); +} + +Quaternion& Quaternion::operator/= (const Quaternion& q) +{ + InvalidateCache(); + + (*this) = (*this) * q.Inverse(); + return (*this); +} + +Vector3d Quaternion::operator*(const Vector3d& p) const +{ + return RotateVector(p); +} + +bool Quaternion::operator== (const Quaternion& q) const +{ + return (v.Similar(q.v)) || (v.Similar(q.v * -1)); +} + +bool Quaternion::operator!= (const Quaternion& q) const +{ + return (!v.Similar(q.v)) && (!v.Similar(q.v * -1)); +} + +Quaternion Quaternion::Inverse() const +{ + if (!isCacheUpToDate_inverse) + { + cache_inverse = (Conjugate() * (1.0 / v.SqrMagnitude())).v; + + isCacheUpToDate_inverse = true; + } + + return Quaternion(cache_inverse); +} + +Quaternion Quaternion::Conjugate() const +{ + return Quaternion(Vector4d(-v.x, -v.y, -v.z, v.w)); +} + +Quaternion Quaternion::UnitQuaternion() const +{ + return (*this) * (1.0 / v.Magnitude()); +} + +Vector3d Quaternion::RotateVector(const Vector3d& vec) const +{ + Quaternion pure(Vector4d(vec.x, vec.y, vec.z, 0)); + + //Quaternion f = Conjugate() * pure * (*this); + //Quaternion f = Inverse().Conjugate() * pure * (this->Inverse()); + + + Quaternion f = Inverse() * pure * (*this); + + Vector3d toRet; + toRet.x = f.v.x; + toRet.y = f.v.y; + toRet.z = f.v.z; + + return toRet; +} + +Vector3d Quaternion::ToEulerAngles() const +{ + if (!isCacheUpToDate_euler) + { + Vector3d euler; + // roll (x-axis rotation) + double sinr_cosp = 2.0 * (v.w * v.x + v.y * v.z); + double cosr_cosp = 1.0 - 2.0 * (v.x * v.x + v.y * v.y); + euler.x = std::atan2(sinr_cosp, cosr_cosp); + + // pitch (y-axis rotation) + double sinp = 2.0 * (v.w * v.y - v.z * v.x); + if (std::abs(sinp) >= 1) + euler.y = std::copysign(PI / 2, sinp); // use 90 degrees if out of range + else + euler.y = std::asin(sinp); + + // yaw (z-axis rotation) + double siny_cosp = 2.0 * (v.w * v.z + v.x * v.y); + double cosy_cosp = 1.0 - 2.0 * (v.y * v.y + v.z * v.z); + euler.z = std::atan2(siny_cosp, cosy_cosp); + + euler *= Rad2Deg; + + cache_euler = euler; + isCacheUpToDate_matrix = true; + } + + return cache_euler; +} + +Matrix4x4 Quaternion::ToRotationMatrix() const +{ + if (!isCacheUpToDate_matrix) + { + Matrix4x4 m; + + const double sqx = v.x * v.x; + const double sqy = v.y * v.y; + const double sqz = v.z * v.z; + const double sqw = v.w * v.w; + const double x = v.x; + const double y = v.y; + const double z = v.z; + const double w = v.w; + + // invs (inverse square length) is only required if quaternion is not already normalised + double invs = 1.0 / (sqx + sqy + sqz + sqw); + + // since sqw + sqx + sqy + sqz =1/invs*invs + + // yaw (y) + m.c = ((2 * x * z) - (2 * w * y)) * invs; + m.f = (1 - (2 * sqx) - (2 * sqz)) * invs; + m.i = ((2 * x * z) + (2 * w * y)) * invs; + + // pitch (x) + m.a = (1 - (2 * sqy) - (2 * sqz)) * invs; + m.g = ((2 * y * z) + (2 * w * x)) * invs; + m.j = ((2 * y * z) - (2 * w * x)) * invs; + + // roll (z) + m.b = ((2 * x * v.y) + (2 * w * z)) * invs; + m.e = ((2 * x * v.y) - (2 * w * z)) * invs; + m.k = (1 - (2 * sqx) - (2 * sqy)) * invs; + + m.p = 1; + + cache_matrix = m; + isCacheUpToDate_matrix = true; + } + + return cache_matrix; +} + +Vector4d Quaternion::GetRawValues() const +{ + return v; +} + +Quaternion Quaternion::AngleBetween(const Quaternion& other) const +{ + return other * Conjugate(); +} + +void Quaternion::SetRawValues(const Vector4d values) +{ + InvalidateCache(); + + v = values; + + return; +} + +Quaternion Quaternion::Lerp(const Quaternion& other, double t) const +{ + return Quaternion(v.Lerp(other.v, t)).UnitQuaternion(); +} + +void Quaternion::InvalidateCache() +{ + isCacheUpToDate_euler = false; + isCacheUpToDate_matrix = false; + isCacheUpToDate_inverse = false; + + return; +} + +namespace Eule +{ + std::ostream& operator<< (std::ostream& os, const Quaternion& q) + { + os << "[" << q.v << "]"; + return os; + } + + std::wostream& operator<<(std::wostream& os, const Quaternion& q) + { + os << L"[" << q.v << L"]"; + return os; + } +} diff --git a/Eule/Quaternion.h b/Eule/Quaternion.h new file mode 100644 index 0000000..8af70c2 --- /dev/null +++ b/Eule/Quaternion.h @@ -0,0 +1,99 @@ +#pragma once +#include "Vector3.h" +#include "Vector4.h" +#include "Matrix4x4.h" + +namespace Eule +{ + /** 3D rotation representation + */ + class Quaternion + { + public: + Quaternion(); + + //! Constructs by these raw values + explicit Quaternion(const Vector4d values); + + //! Copies this existing Quaternion + Quaternion(const Quaternion& q); + + //! Creates an quaternion from euler angles + Quaternion(const Vector3d eulerAngles); + + ~Quaternion(); + + //! Copies + Quaternion operator= (const Quaternion& q); + + //! Multiplies (applies) + Quaternion operator* (const Quaternion& q) const; + + //! Divides (applies) + Quaternion operator/ (Quaternion& q) const; + + //! Also multiplies + Quaternion& operator*= (const Quaternion& q); + + //! Also divides + Quaternion& operator/= (const Quaternion& q); + + //! Will transform a 3d point around its origin + Vector3d operator* (const Vector3d& p) const; + + bool operator== (const Quaternion& q) const; + bool operator!= (const Quaternion& q) const; + + Quaternion Inverse() const; + + Quaternion Conjugate() const; + + Quaternion UnitQuaternion() const; + + //! Will rotate a vector by this quaternion + Vector3d RotateVector(const Vector3d& vec) const; + + //! Will return euler angles representing this Quaternion's rotation + Vector3d ToEulerAngles() const; + + //! Will return a rotation matrix representing this Quaternions rotation + Matrix4x4 ToRotationMatrix() const; + + //! Will return the raw four-dimensional values + Vector4d GetRawValues() const; + + //! Will return the value between two Quaternion's as another Quaternion + Quaternion AngleBetween(const Quaternion& other) const; + + //! Will set the raw four-dimensional values + void SetRawValues(const Vector4d values); + + //! Will return the lerp result between two quaternions + Quaternion Lerp(const Quaternion& other, double t) const; + + friend std::ostream& operator<< (std::ostream& os, const Quaternion& q); + friend std::wostream& operator<< (std::wostream& os, const Quaternion& q); + + private: + //! Scales + Quaternion operator* (const double scale) const; + Quaternion& operator*= (const double scale); + + //! Quaternion values + Vector4d v; + + //! Will force a regenartion of the euler and matrix caches on further converter calls + void InvalidateCache(); + + // Caches for conversions + mutable bool isCacheUpToDate_euler = false; + mutable Vector3d cache_euler; + + mutable bool isCacheUpToDate_matrix = false; + mutable Matrix4x4 cache_matrix; + + mutable bool isCacheUpToDate_inverse = false; + mutable Vector4d cache_inverse; + + }; +} diff --git a/Eule/Rect.h b/Eule/Rect.h new file mode 100644 index 0000000..05c2039 --- /dev/null +++ b/Eule/Rect.h @@ -0,0 +1,13 @@ +#pragma once +#include "../Eule/Vector2.h" + +namespace Eule +{ + /** Trivial data structure representing a rectangle + */ + struct Rect + { + Vector2d pos; + Vector2d size; + }; +} diff --git a/Eule/TrapazoidalPrismCollider.cpp b/Eule/TrapazoidalPrismCollider.cpp new file mode 100644 index 0000000..f423812 --- /dev/null +++ b/Eule/TrapazoidalPrismCollider.cpp @@ -0,0 +1,110 @@ +#include "TrapazoidalPrismCollider.h" + +using namespace Eule; + +TrapazoidalPrismCollider::TrapazoidalPrismCollider() +{ + return; +} + +void TrapazoidalPrismCollider::operator=(const TrapazoidalPrismCollider& other) +{ + vertices = other.vertices; + faceNormals = other.faceNormals; + + return; +} + +void TrapazoidalPrismCollider::operator=(TrapazoidalPrismCollider&& other) noexcept +{ + vertices = std::move(other.vertices); + faceNormals = std::move(other.faceNormals); + + return; +} + +const Vector3d& TrapazoidalPrismCollider::GetVertex(std::size_t index) const +{ + return vertices[index]; +} + +void TrapazoidalPrismCollider::SetVertex(std::size_t index, const Vector3d value) +{ + vertices[index] = value; + GenerateNormalsFromVertices(); + return; +} + +void TrapazoidalPrismCollider::GenerateNormalsFromVertices() +{ + faceNormals[(std::size_t)FACE_NORMALS::LEFT] = + (vertices[BACK|LEFT|BOTTOM] - vertices[FRONT|LEFT|BOTTOM]) + .CrossProduct(vertices[FRONT|LEFT|TOP] - vertices[FRONT|LEFT|BOTTOM]); + + faceNormals[(std::size_t)FACE_NORMALS::RIGHT] = + (vertices[FRONT|RIGHT|TOP] - vertices[FRONT|RIGHT|BOTTOM]) + .CrossProduct(vertices[BACK|RIGHT|BOTTOM] - vertices[FRONT|RIGHT|BOTTOM]); + + faceNormals[(std::size_t)FACE_NORMALS::FRONT] = + (vertices[FRONT|LEFT|TOP] - vertices[FRONT|LEFT|BOTTOM]) + .CrossProduct(vertices[FRONT|RIGHT|BOTTOM] - vertices[FRONT|LEFT|BOTTOM]); + + faceNormals[(std::size_t)FACE_NORMALS::BACK] = + (vertices[BACK|RIGHT|BOTTOM] - vertices[BACK|LEFT|BOTTOM]) + .CrossProduct(vertices[BACK|LEFT|TOP] - vertices[BACK|LEFT|BOTTOM]); + + faceNormals[(std::size_t)FACE_NORMALS::TOP] = + (vertices[BACK|LEFT|TOP] - vertices[FRONT|LEFT|TOP]) + .CrossProduct(vertices[FRONT|RIGHT|TOP] - vertices[FRONT|LEFT|TOP]); + + faceNormals[(std::size_t)FACE_NORMALS::BOTTOM] = + (vertices[FRONT|RIGHT|BOTTOM] - vertices[FRONT|LEFT|BOTTOM]) + .CrossProduct(vertices[BACK|LEFT|BOTTOM] - vertices[FRONT|LEFT|BOTTOM]); + + return; +} + +double TrapazoidalPrismCollider::FaceDot(FACE_NORMALS face, const Vector3d& point) const +{ + // This vertex is the one being used twice to calculate the normals + std::size_t coreVertexIdx; + switch (face) + { + case FACE_NORMALS::LEFT: + coreVertexIdx = FRONT|LEFT|BOTTOM; + break; + + case FACE_NORMALS::RIGHT: + coreVertexIdx = FRONT|RIGHT|BOTTOM; + break; + + case FACE_NORMALS::FRONT: + coreVertexIdx = FRONT|LEFT|BOTTOM; + break; + + case FACE_NORMALS::BACK: + coreVertexIdx = BACK|LEFT|BOTTOM; + break; + + case FACE_NORMALS::TOP: + coreVertexIdx = FRONT|LEFT|TOP; + break; + + case FACE_NORMALS::BOTTOM: + coreVertexIdx = FRONT|LEFT|BOTTOM; + break; + } + + if ((std::size_t)face < 6) + return faceNormals[(std::size_t)face].DotProduct(point - vertices[coreVertexIdx]); + return 1; +} + +bool TrapazoidalPrismCollider::Contains(const Vector3d& point) const +{ + for (std::size_t i = 0; i < 6; i++) + if (FaceDot((FACE_NORMALS)i, point) < 0) + return false; + + return true; +} diff --git a/Eule/TrapazoidalPrismCollider.h b/Eule/TrapazoidalPrismCollider.h new file mode 100644 index 0000000..1dda614 --- /dev/null +++ b/Eule/TrapazoidalPrismCollider.h @@ -0,0 +1,63 @@ +#pragma once +#include "Vector3.h" +#include "Collider.h" +#include + +namespace Eule +{ + /** A collider describing a trapazoidal prism. + * A trapazoidal prism is basically a box, but each vertex can be manipulated individually, altering + * the angles between faces. + * Distorting a 2d face into 3d space will result in undefined behaviour. Each face should stay flat, relative to itself. This shape is based on QUADS! + */ + class TrapazoidalPrismCollider : public Collider + { + public: + TrapazoidalPrismCollider(); + TrapazoidalPrismCollider(const TrapazoidalPrismCollider& other) = default; + TrapazoidalPrismCollider(TrapazoidalPrismCollider&& other) noexcept = default; + void operator=(const TrapazoidalPrismCollider& other); + void operator=(TrapazoidalPrismCollider&& other) noexcept; + + //! Will return a specific vertex + const Vector3d& GetVertex(std::size_t index) const; + + //! Will set the value of a specific vertex + void SetVertex(std::size_t index, const Vector3d value); + + //! Tests, if this Collider contains a point + bool Contains(const Vector3d& point) const override; + + /* Vertex identifiers */ + static constexpr std::size_t BACK = 0; + static constexpr std::size_t FRONT = 4; + static constexpr std::size_t LEFT = 0; + static constexpr std::size_t RIGHT = 2; + static constexpr std::size_t BOTTOM = 0; + static constexpr std::size_t TOP = 1; + + private: + enum class FACE_NORMALS : std::size_t; + + //! Will calculate the vertex normals from vertices + void GenerateNormalsFromVertices(); + + //! Returns the dot product of a given point against a specific plane of the bounding box + double FaceDot(FACE_NORMALS face, const Vector3d& point) const; + + std::array vertices; + + + // Face normals + enum class FACE_NORMALS : std::size_t + { + LEFT = 0, + RIGHT = 1, + FRONT = 2, + BACK = 3, + TOP = 4, + BOTTOM = 5 + }; + std::array faceNormals; + }; +} diff --git a/Eule/Vector2.cpp b/Eule/Vector2.cpp new file mode 100644 index 0000000..dcfb857 --- /dev/null +++ b/Eule/Vector2.cpp @@ -0,0 +1,700 @@ +#include "Vector2.h" +#include "Math.h" +#include + +//#define _EULE_NO_INTRINSICS_ +#ifndef _EULE_NO_INTRINSICS_ +#include +#endif + +using namespace Eule; + +/* + NOTE: + Here you will find bad, unoptimized methods for T=int. + This is because the compiler needs a method for each type in each instantiation of the template! + I can't generalize the methods when heavily optimizing for doubles. + These functions will get called VERY rarely, if ever at all, for T=int, so it's ok. + The T=int instantiation only exists to store a value-pair of two ints. Not so-much as a vector in terms of vector calculus. +*/ + +// Good, optimized chad version for doubles +double Vector2::DotProduct(const Vector2& other) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components into registers + __m256 __vector_self = _mm256_set_ps(0,0,0,0,0,0, (float)y, (float)x); + __m256 __vector_other = _mm256_set_ps(0,0,0,0,0,0, (float)other.y, (float)other.x); + + // Define bitmask, and execute computation + const int mask = 0x31; // -> 0011 1000 -> use positions 0011 (last 2) of the vectors supplied, and place them in 1000 (first only) element of __dot + __m256 __dot = _mm256_dp_ps(__vector_self, __vector_other, mask); + + // Retrieve result, and return it + float result[8]; + _mm256_storeu_ps(result, __dot); + + return result[0]; + + #else + return (x * other.x) + + (y * other.y); + #endif +} + +// Slow, lame version for intcels +double Vector2::DotProduct(const Vector2& other) const +{ + int iDot = (x * other.x) + + (y * other.y); + + return (double)iDot; +} + + + +// Good, optimized chad version for doubles +double Vector2::CrossProduct(const Vector2& other) const +{ + return (x * other.y) - + (y * other.x); +} + +// Slow, lame version for intcels +double Vector2::CrossProduct(const Vector2& other) const +{ + int iCross = (x * other.y) - + (y * other.x); + + return (double)iCross; +} + + + +// Good, optimized chad version for doubles +double Vector2::SqrMagnitude() const +{ + // x.DotProduct(x) == x.SqrMagnitude() + return DotProduct(*this); +} + +// Slow, lame version for intcels +double Vector2::SqrMagnitude() const +{ + int iSqrMag = x*x + y*y; + return (double)iSqrMag; +} + +template +double Vector2::Magnitude() const +{ + return sqrt(SqrMagnitude()); +} + + + +Vector2 Vector2::VectorScale(const Vector2& scalar) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Load vectors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __vector_scalar = _mm256_set_pd(0, 0, scalar.y, scalar.x); + + // Multiply them + __m256d __product = _mm256_mul_pd(__vector_self, __vector_scalar); + + // Retrieve result + double result[4]; + _mm256_storeu_pd(result, __product); + + // Return value + return Vector2( + result[0], + result[1] + ); + + #else + + return Vector2( + x * scalar.x, + y * scalar.y + ); + #endif +} + +Vector2 Vector2::VectorScale(const Vector2& scalar) const +{ + return Vector2( + x * scalar.x, + y * scalar.y + ); +} + + +template +Vector2 Vector2::Normalize() const +{ + Vector2 norm(x, y); + norm.NormalizeSelf(); + + return norm; +} + +// Method to normalize a Vector2d +void Vector2::NormalizeSelf() +{ + double length = Magnitude(); + + // Prevent division by 0 + if (length == 0) + { + x = 0; + y = 0; + } + else + { + #ifndef _EULE_NO_INTRINSICS_ + + // Load vector and length into registers + __m256d __vec = _mm256_set_pd(0, 0, y, x); + __m256d __len = _mm256_set1_pd(length); + + // Divide + __m256d __prod = _mm256_div_pd(__vec, __len); + + // Extract and set values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + x = prod[0]; + y = prod[1]; + + #else + + x /= length; + y /= length; + + #endif + } + + return; +} + +// You can't normalize an int vector, ffs! +// But we need an implementation for T=int +void Vector2::NormalizeSelf() +{ + std::cerr << "Stop normalizing int-vectors!!" << std::endl; + x = 0; + y = 0; + + return; +} + + +// Good, optimized chad version for doubles +void Vector2::LerpSelf(const Vector2& other, double t) +{ + const double it = 1.0 - t; // Inverse t + + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __vector_other = _mm256_set_pd(0, 0, other.y, other.x); + __m256d __t = _mm256_set1_pd(t); + __m256d __it = _mm256_set1_pd(it); // Inverse t + + // Procedure: + // (__vector_self * __it) + (__vector_other * __t) + + __m256d __sum = _mm256_set1_pd(0); // this will hold the sum of the two multiplications + + __sum = _mm256_fmadd_pd(__vector_self, __it, __sum); + __sum = _mm256_fmadd_pd(__vector_other, __t, __sum); + + // Retrieve result, and apply it + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + x = sum[0]; + y = sum[1]; + + #else + + x = it * x + t * other.x; + y = it * y + t * other.y; + + #endif + + return; +} + + + +// Slow, lame version for intcels +void Vector2::LerpSelf(const Vector2& other, double t) +{ + const double it = 1.0 - t; // Inverse t + + x = (int)(it * (double)x + t * (double)other.x); + y = (int)(it * (double)y + t * (double)other.y); + + return; +} + +Vector2 Vector2::Lerp(const Vector2& other, double t) const +{ + Vector2d copy(*this); + copy.LerpSelf(other, t); + + return copy; +} + +Vector2 Vector2::Lerp(const Vector2& other, double t) const +{ + Vector2d copy(this->ToDouble()); + copy.LerpSelf(other.ToDouble(), t); + + return copy; +} + + + +template +T& Vector2::operator[](std::size_t idx) +{ + switch (idx) + { + case 0: + return x; + case 1: + return y; + default: + throw std::out_of_range("Array descriptor on Vector2 out of range!"); + } +} + +template +const T& Vector2::operator[](std::size_t idx) const +{ + switch (idx) + { + case 0: + return x; + case 1: + return y; + default: + throw std::out_of_range("Array descriptor on Vector2 out of range!"); + } +} + +template +bool Vector2::Similar(const Vector2& other, double epsilon) const +{ + return + (::Math::Similar(x, other.x, epsilon)) && + (::Math::Similar(y, other.y, epsilon)) + ; +} + +template +Vector2 Vector2::ToInt() const +{ + return Vector2((int)x, (int)y); +} + +template +Vector2 Vector2::ToDouble() const +{ + return Vector2((double)x, (double)y); +} + + +Vector2 Vector2::operator+(const Vector2& other) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __vector_other = _mm256_set_pd(0, 0, other.y, other.x); + + // Add the components + __m256d __sum = _mm256_add_pd(__vector_self, __vector_other); + + // Retrieve and return these values + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + return Vector2( + sum[0], + sum[1] + ); + + #else + + return Vector2( + x + other.x, + y + other.y + ); + #endif +} + +template +Vector2 Vector2::operator+(const Vector2& other) const +{ + return Vector2( + x + other.x, + y + other.y + ); +} + + + +void Vector2::operator+=(const Vector2& other) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __vector_other = _mm256_set_pd(0, 0, other.y, other.x); + + // Add the components + __m256d __sum = _mm256_add_pd(__vector_self, __vector_other); + + // Retrieve and apply these values + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + x = sum[0]; + y = sum[1]; + + #else + + x += other.x; + y += other.y; + + #endif + + return; +} + +template +void Vector2::operator+=(const Vector2& other) +{ + x += other.x; + y += other.y; + return; +} + + + +Vector2 Vector2::operator-(const Vector2& other) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __vector_other = _mm256_set_pd(0, 0, other.y, other.x); + + // Subtract the components + __m256d __diff = _mm256_sub_pd(__vector_self, __vector_other); + + // Retrieve and return these values + double diff[4]; + _mm256_storeu_pd(diff, __diff); + + return Vector2( + diff[0], + diff[1] + ); + + #else + + return Vector2( + x - other.x, + y - other.y + ); + #endif +} + +template +Vector2 Vector2::operator-(const Vector2& other) const +{ + return Vector2( + x - other.x, + y - other.y + ); +} + + + +void Vector2::operator-=(const Vector2& other) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __vector_other = _mm256_set_pd(0, 0, other.y, other.x); + + // Subtract the components + __m256d __diff = _mm256_sub_pd(__vector_self, __vector_other); + + // Retrieve and apply these values + double diff[4]; + _mm256_storeu_pd(diff, __diff); + + x = diff[0]; + y = diff[1]; + + #else + + x -= other.x; + y -= other.y; + + #endif + + return; +} + +template +void Vector2::operator-=(const Vector2& other) +{ + x -= other.x; + y -= other.y; + return; +} + + + +Vector2 Vector2::operator*(const double scale) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Multiply the components + __m256d __prod = _mm256_mul_pd(__vector_self, __scalar); + + // Retrieve and return these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + return Vector2( + prod[0], + prod[1] + ); + + #else + + return Vector2( + x * scale, + y * scale + ); + + #endif +} + +template +Vector2 Vector2::operator*(const T scale) const +{ + return Vector2( + x * scale, + y * scale + ); +} + + + +void Vector2::operator*=(const double scale) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Multiply the components + __m256d __prod = _mm256_mul_pd(__vector_self, __scalar); + + // Retrieve and apply these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + x = prod[0]; + y = prod[1]; + + #else + + x *= scale; + y *= scale; + + #endif + + return; +} + +template +void Vector2::operator*=(const T scale) +{ + x *= scale; + y *= scale; + return; +} + + + +Vector2 Vector2::operator/(const double scale) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Divide the components + __m256d __prod = _mm256_div_pd(__vector_self, __scalar); + + // Retrieve and return these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + return Vector2( + prod[0], + prod[1] + ); + + #else + + return Vector2( + x / scale, + y / scale + ); + + #endif +} + +template +Vector2 Vector2::operator/(const T scale) const +{ + return Vector2( + x / scale, + y / scale + ); +} + + + +void Vector2::operator/=(const double scale) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, 0, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Divide the components + __m256d __prod = _mm256_div_pd(__vector_self, __scalar); + + // Retrieve and apply these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + x = prod[0]; + y = prod[1]; + + #else + + x /= scale; + y /= scale; + + #endif + return; +} + +template +void Vector2::operator/=(const T scale) +{ + x /= scale; + y /= scale; + return; +} + + + +template +void Vector2::operator=(const Vector2& other) +{ + x = other.x; + y = other.y; + + return; +} + +template +void Vector2::operator=(Vector2&& other) noexcept +{ + x = std::move(other.x); + y = std::move(other.y); + + return; +} + +template +bool Vector2::operator==(const Vector2& other) const +{ + return + (x == other.x) && + (y == other.y); +} + +template +bool Vector2::operator!=(const Vector2& other) const +{ + return !operator==(other); +} + +template +Vector2 Vector2::operator-() const +{ + return Vector2( + -x, + -y + ); +} + +// Don't want these includes above the other stuff +#include "Vector3.h" +#include "Vector4.h" +template +Vector2::operator Vector3() const +{ + return Vector3(x, y, 0); +} + +template +Vector2::operator Vector4() const +{ + return Vector4(x, y, 0, 0); +} + +template class Vector2; +template class Vector2; + +// Some handy predefines +template +const Vector2 Vector2::up(0, 1); +template +const Vector2 Vector2::down(0, -1); +template +const Vector2 Vector2::right(1, 0); +template +const Vector2 Vector2::left(-1, 0); +template +const Vector2 Vector2::one(1, 1); +template +const Vector2 Vector2::zero(0, 0); \ No newline at end of file diff --git a/Eule/Vector2.h b/Eule/Vector2.h new file mode 100644 index 0000000..de2c6fa --- /dev/null +++ b/Eule/Vector2.h @@ -0,0 +1,103 @@ +#pragma once +#include +#include + +namespace Eule +{ + template class Vector3; + template class Vector4; + + /** Representation of a 2d vector. + * Contains a lot of utility methods. + */ + template + class Vector2 + { + public: + Vector2() : x{ 0 }, y{ 0 } {} + Vector2(T _x, T _y) : x{ _x }, y{ _y } {} + Vector2(const Vector2& other) = default; + Vector2(Vector2&& other) noexcept = default; + + //! Will compute the dot product to another Vector2 + double DotProduct(const Vector2& other) const; + + //! Will compute the cross product to another Vector2 + double CrossProduct(const Vector2& other) const; + + //! Will compute the square magnitude + double SqrMagnitude() const; + + //! Will compute the magnitude + double Magnitude() const; + + //! Will return the normalization of this vector + [[nodiscard]] Vector2 Normalize() const; + + //! Will normalize this vector + void NormalizeSelf(); + + //! Will scale self.n by scalar.n + Vector2 VectorScale(const Vector2& scalar) const; + + //! Will lerp itself towards other by t + void LerpSelf(const Vector2& other, double t); + + //! Will return a lerp result between this and another vector + [[nodiscard]] Vector2 Lerp(const Vector2& other, double t) const; + + //! Will compare if two vectors are similar to a certain epsilon value + [[nodiscard]] bool Similar(const Vector2& other, double epsilon = 0.00001) const; + + //! Will convert this vector to a Vector2i + [[nodiscard]] Vector2 ToInt() const; + + //! Will convert this vector to a Vector2d + [[nodiscard]] Vector2 ToDouble() const; + + T& operator[](std::size_t idx); + const T& operator[](std::size_t idx) const; + + Vector2 operator+(const Vector2& other) const; + void operator+=(const Vector2& other); + Vector2 operator-(const Vector2& other) const; + void operator-=(const Vector2& other); + Vector2 operator*(const T scale) const; + void operator*=(const T scale); + Vector2 operator/(const T scale) const; + void operator/=(const T scale); + Vector2 operator-() const; + + operator Vector3() const; //! Conversion method + operator Vector4() const; //! Conversion method + + void operator=(const Vector2& other); + void operator=(Vector2&& other) noexcept; + + bool operator==(const Vector2& other) const; + bool operator!=(const Vector2& other) const; + + friend std::ostream& operator<< (std::ostream& os, const Vector2& v) + { + return os << "[x: " << v.x << " y: " << v.y << "]"; + } + friend std::wostream& operator<< (std::wostream& os, const Vector2& v) + { + return os << L"[x: " << v.x << L" y: " << v.y << L"]"; + } + + T x; + T y; + + // Some handy predefines + static const Vector2 up; + static const Vector2 down; + static const Vector2 right; + static const Vector2 left; + static const Vector2 one; + static const Vector2 zero; + }; + + typedef Vector2 Vector2i; + typedef Vector2 Vector2d; +} diff --git a/Eule/Vector3.cpp b/Eule/Vector3.cpp new file mode 100644 index 0000000..1cb9e62 --- /dev/null +++ b/Eule/Vector3.cpp @@ -0,0 +1,903 @@ +#include "Vector3.h" +#include "Math.h" +#include + +//#define _EULE_NO_INTRINSICS_ +#ifndef _EULE_NO_INTRINSICS_ +#include +#endif + +using namespace Eule; + +/* + NOTE: + Here you will find bad, unoptimized methods for T=int. + This is because the compiler needs a method for each type in each instantiation of the template! + I can't generalize the methods when heavily optimizing for doubles. + These functions will get called VERY rarely, if ever at all, for T=int, so it's ok. + The T=int instantiation only exists to store a value-pair of two ints. Not so-much as a vector in terms of vector calculus. +*/ + +// Good, optimized chad version for doubles +double Vector3::DotProduct(const Vector3& other) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components into registers + __m256 __vector_self = _mm256_set_ps(0,0,0,0,0, (float)z, (float)y, (float)x); + __m256 __vector_other = _mm256_set_ps(0,0,0,0,0, (float)other.z, (float)other.y, (float)other.x); + + // Define bitmask, and execute computation + const int mask = 0x71; // -> 0111 1000 -> use positions 0111 (last 3) of the vectors supplied, and place them in 1000 (first only) element of __dot + __m256 __dot = _mm256_dp_ps(__vector_self, __vector_other, mask); + + // Retrieve result, and return it + float result[8]; + _mm256_storeu_ps(result, __dot); + + return result[0]; + + #else + return (x * other.x) + + (y * other.y) + + (z * other.z); + #endif +} + +// Slow, lame version for intcels +double Vector3::DotProduct(const Vector3& other) const +{ + int iDot = (x * other.x) + (y * other.y) + (z * other.z); + return (double)iDot; +} + + + +// Good, optimized chad version for doubles +Vector3 Vector3::CrossProduct(const Vector3& other) const +{ + Vector3 cp; + cp.x = (y * other.z) - (z * other.y); + cp.y = (z * other.x) - (x * other.z); + cp.z = (x * other.y) - (y * other.x); + + return cp; +} + +// Slow, lame version for intcels +Vector3 Vector3::CrossProduct(const Vector3& other) const +{ + Vector3 cp; + cp.x = ((double)y * (double)other.z) - ((double)z * (double)other.y); + cp.y = ((double)z * (double)other.x) - ((double)x * (double)other.z); + cp.z = ((double)x * (double)other.y) - ((double)y * (double)other.x); + + return cp; +} + + + +// Good, optimized chad version for doubles +double Vector3::SqrMagnitude() const +{ + // x.DotProduct(x) == x.SqrMagnitude() + return DotProduct(*this); +} + +// Slow, lame version for intcels +double Vector3::SqrMagnitude() const +{ + int iSqrMag = x*x + y*y + z*z; + return (double)iSqrMag; +} + +template +double Vector3::Magnitude() const +{ + return sqrt(SqrMagnitude()); +} + + + +Vector3 Vector3::VectorScale(const Vector3& scalar) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Load vectors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __vector_scalar = _mm256_set_pd(0, scalar.z, scalar.y, scalar.x); + + // Multiply them + __m256d __product = _mm256_mul_pd(__vector_self, __vector_scalar); + + // Retrieve result + double result[4]; + _mm256_storeu_pd(result, __product); + + // Return value + return Vector3( + result[0], + result[1], + result[2] + ); + + #else + + return Vector3( + x * scalar.x, + y * scalar.y, + z * scalar.z + ); + + #endif +} + +Vector3 Vector3::VectorScale(const Vector3& scalar) const +{ + return Vector3( + x * scalar.x, + y * scalar.y, + z * scalar.z + ); +} + + + +template +Vector3 Vector3::Normalize() const +{ + Vector3 norm(x, y, z); + norm.NormalizeSelf(); + + return norm; +} + +// Method to normalize a Vector3d +void Vector3::NormalizeSelf() +{ + const double length = Magnitude(); + + // Prevent division by 0 + if (length == 0) + { + x = 0; + y = 0; + z = 0; + } + else + { + #ifndef _EULE_NO_INTRINSICS_ + + // Load vector and length into registers + __m256d __vec = _mm256_set_pd(0, z, y, x); + __m256d __len = _mm256_set1_pd(length); + + // Divide + __m256d __prod = _mm256_div_pd(__vec, __len); + + // Extract and set values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + x = prod[0]; + y = prod[1]; + z = prod[2]; + + #else + + x /= length; + y /= length; + z /= length; + + #endif + } + + return; +} + +// You can't normalize an int vector, ffs! +// But we need an implementation for T=int +void Vector3::NormalizeSelf() +{ + std::cerr << "Stop normalizing int-vectors!!" << std::endl; + x = 0; + y = 0; + z = 0; + + return; +} + + + +template +bool Vector3::Similar(const Vector3& other, double epsilon) const +{ + return + (::Math::Similar(x, other.x, epsilon)) && + (::Math::Similar(y, other.y, epsilon)) && + (::Math::Similar(z, other.z, epsilon)) + ; +} + +template +Vector3 Vector3::ToInt() const +{ + return Vector3((int)x, (int)y, (int)z); +} + +template +Vector3 Vector3::ToDouble() const +{ + return Vector3((double)x, (double)y, (double)z); +} + +template +T& Vector3::operator[](std::size_t idx) +{ + switch (idx) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + default: + throw std::out_of_range("Array descriptor on Vector3 out of range!"); + } +} + +template +const T& Vector3::operator[](std::size_t idx) const +{ + switch (idx) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + default: + throw std::out_of_range("Array descriptor on Vector3 out of range!"); + } +} + + + +// Good, optimized chad version for doubles +void Vector3::LerpSelf(const Vector3& other, double t) +{ + const double it = 1.0 - t; // Inverse t + + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __vector_other = _mm256_set_pd(0, other.z, other.y, other.x); + __m256d __t = _mm256_set1_pd(t); + __m256d __it = _mm256_set1_pd(it); // Inverse t + + // Procedure: + // (__vector_self * __it) + (__vector_other * __t) + + __m256d __sum = _mm256_set1_pd(0); // this will hold the sum of the two multiplications + + __sum = _mm256_fmadd_pd(__vector_self, __it, __sum); + __sum = _mm256_fmadd_pd(__vector_other, __t, __sum); + + // Retrieve result, and apply it + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + x = sum[0]; + y = sum[1]; + z = sum[2]; + + #else + + x = it*x + t*other.x; + y = it*y + t*other.y; + z = it*z + t*other.z; + + #endif + + return; +} + + + +// Slow, lame version for intcels +void Vector3::LerpSelf(const Vector3& other, double t) +{ + const double it = 1.0 - t; // Inverse t + + x = (int)(it * (double)x + t * (double)other.x); + y = (int)(it * (double)y + t * (double)other.y); + z = (int)(it * (double)z + t * (double)other.z); + + return; +} + +Vector3 Vector3::Lerp(const Vector3& other, double t) const +{ + Vector3d copy(*this); + copy.LerpSelf(other, t); + + return copy; +} + +Vector3 Vector3::Lerp(const Vector3& other, double t) const +{ + Vector3d copy(this->ToDouble()); + copy.LerpSelf(other.ToDouble(), t); + + return copy; +} + + + +Vector3 Vector3::operator+(const Vector3& other) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __vector_other = _mm256_set_pd(0, other.z, other.y, other.x); + + // Add the components + __m256d __sum = _mm256_add_pd(__vector_self, __vector_other); + + // Retrieve and return these values + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + return Vector3( + sum[0], + sum[1], + sum[2] + ); + + #else + + return Vector3( + x + other.x, + y + other.y, + z + other.z + ); + #endif +} + +template +Vector3 Vector3::operator+(const Vector3& other) const +{ + return Vector3( + x + other.x, + y + other.y, + z + other.z + ); +} + + + +void Vector3::operator+=(const Vector3& other) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __vector_other = _mm256_set_pd(0, other.z, other.y, other.x); + + // Add the components + __m256d __sum = _mm256_add_pd(__vector_self, __vector_other); + + // Retrieve and apply these values + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + x = sum[0]; + y = sum[1]; + z = sum[2]; + + #else + + x += other.x; + y += other.y; + z += other.z; + + #endif + + return; +} + +template +void Vector3::operator+=(const Vector3& other) +{ + x += other.x; + y += other.y; + z += other.z; + return; +} + + + +Vector3 Vector3::operator-(const Vector3& other) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __vector_other = _mm256_set_pd(0, other.z, other.y, other.x); + + // Subtract the components + __m256d __diff = _mm256_sub_pd(__vector_self, __vector_other); + + // Retrieve and return these values + double diff[4]; + _mm256_storeu_pd(diff, __diff); + + return Vector3( + diff[0], + diff[1], + diff[2] + ); + + #else + + return Vector3( + x - other.x, + y - other.y, + z - other.z + ); + #endif +} + +template +Vector3 Vector3::operator-(const Vector3& other) const +{ + return Vector3( + x - other.x, + y - other.y, + z - other.z + ); +} + + + +void Vector3::operator-=(const Vector3& other) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __vector_other = _mm256_set_pd(0, other.z, other.y, other.x); + + // Subtract the components + __m256d __diff = _mm256_sub_pd(__vector_self, __vector_other); + + // Retrieve and apply these values + double diff[4]; + _mm256_storeu_pd(diff, __diff); + + x = diff[0]; + y = diff[1]; + z = diff[2]; + + #else + + x -= other.x; + y -= other.y; + z -= other.z; + + #endif + + return; +} + +template +void Vector3::operator-=(const Vector3& other) +{ + x -= other.x; + y -= other.y; + z -= other.z; + return; +} + + + +Vector3 Vector3::operator*(const double scale) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Multiply the components + __m256d __prod = _mm256_mul_pd(__vector_self, __scalar); + + // Retrieve and return these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + return Vector3( + prod[0], + prod[1], + prod[2] + ); + + #else + + return Vector3( + x * scale, + y * scale, + z * scale + ); + + #endif +} + +template +Vector3 Vector3::operator*(const T scale) const +{ + return Vector3( + x * scale, + y * scale, + z * scale + ); +} + + + +void Vector3::operator*=(const double scale) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Multiply the components + __m256d __prod = _mm256_mul_pd(__vector_self, __scalar); + + // Retrieve and apply these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + x = prod[0]; + y = prod[1]; + z = prod[2]; + + #else + + x *= scale; + y *= scale; + z *= scale; + + #endif + + return; +} + +template +void Vector3::operator*=(const T scale) +{ + x *= scale; + y *= scale; + z *= scale; + return; +} + + + +Vector3 Vector3::operator/(const double scale) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Divide the components + __m256d __prod = _mm256_div_pd(__vector_self, __scalar); + + // Retrieve and return these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + return Vector3( + prod[0], + prod[1], + prod[2] + ); + + #else + + return Vector3( + x / scale, + y / scale, + z / scale + ); + + #endif +} + +template +Vector3 Vector3::operator/(const T scale) const +{ + return Vector3( + x / scale, + y / scale, + z / scale + ); +} + + + +void Vector3::operator/=(const double scale) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(0, z, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Divide the components + __m256d __prod = _mm256_div_pd(__vector_self, __scalar); + + // Retrieve and apply these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + x = prod[0]; + y = prod[1]; + z = prod[2]; + + #else + + x /= scale; + y /= scale; + z /= scale; + + #endif + return; +} + +template +void Vector3::operator/=(const T scale) +{ + x /= scale; + y /= scale; + z /= scale; + return; +} + + + +// Good, optimized chad version for doubles +Vector3 Vector3::operator*(const Matrix4x4& mat) const +{ + Vector3 newVec; + + #ifndef _EULE_NO_INTRINSICS_ + // Store x, y, and z values + __m256d __vecx = _mm256_set1_pd(x); + __m256d __vecy = _mm256_set1_pd(y); + __m256d __vecz = _mm256_set1_pd(z); + + // Store matrix values + __m256d __mat_row0 = _mm256_set_pd(mat[0][0], mat[1][0], mat[2][0], 0); + __m256d __mat_row1 = _mm256_set_pd(mat[0][1], mat[1][1], mat[2][1], 0); + __m256d __mat_row2 = _mm256_set_pd(mat[0][2], mat[1][2], mat[2][2], 0); + + // Multiply x, y, z and matrix values + __m256d __mul_vecx_row0 = _mm256_mul_pd(__vecx, __mat_row0); + __m256d __mul_vecy_row1 = _mm256_mul_pd(__vecy, __mat_row1); + __m256d __mul_vecz_row2 = _mm256_mul_pd(__vecz, __mat_row2); + + // Sum up the products + __m256d __sum = _mm256_add_pd(__mul_vecx_row0, _mm256_add_pd(__mul_vecy_row1, __mul_vecz_row2)); + + // Store translation values + __m256d __translation = _mm256_set_pd(mat[0][3], mat[1][3], mat[2][3], 0); + + // Add the translation values + __m256d __final = _mm256_add_pd(__sum, __translation); + + double dfinal[4]; + + _mm256_storeu_pd(dfinal, __final); + + newVec.x = dfinal[3]; + newVec.y = dfinal[2]; + newVec.z = dfinal[1]; + + #else + // Rotation, Scaling + newVec.x = (mat[0][0] * x) + (mat[1][0] * y) + (mat[2][0] * z); + newVec.y = (mat[0][1] * x) + (mat[1][1] * y) + (mat[2][1] * z); + newVec.z = (mat[0][2] * x) + (mat[1][2] * y) + (mat[2][2] * z); + + // Translation + newVec.x += mat[0][3]; + newVec.y += mat[1][3]; + newVec.z += mat[2][3]; + #endif + + return newVec; +} + +// Slow, lame version for intcels +Vector3 Vector3::operator*(const Matrix4x4& mat) const +{ + Vector3 newVec; + + // Rotation, Scaling + newVec.x = ((mat[0][0] * x) + (mat[1][0] * y) + (mat[2][0] * z)); + newVec.y = ((mat[0][1] * x) + (mat[1][1] * y) + (mat[2][1] * z)); + newVec.z = ((mat[0][2] * x) + (mat[1][2] * y) + (mat[2][2] * z)); + + // Translation + newVec.x += mat[0][3]; + newVec.y += mat[1][3]; + newVec.z += mat[2][3]; + + return Vector3( + (int)newVec.x, + (int)newVec.y, + (int)newVec.z + ); +} + + + +// Good, optimized chad version for doubles +void Vector3::operator*=(const Matrix4x4& mat) +{ + #ifndef _EULE_NO_INTRINSICS_ + // Store x, y, and z values + __m256d __vecx = _mm256_set1_pd(x); + __m256d __vecy = _mm256_set1_pd(y); + __m256d __vecz = _mm256_set1_pd(z); + + // Store matrix values + __m256d __mat_row0 = _mm256_set_pd(mat[0][0], mat[1][0], mat[2][0], 0); + __m256d __mat_row1 = _mm256_set_pd(mat[0][1], mat[1][1], mat[2][1], 0); + __m256d __mat_row2 = _mm256_set_pd(mat[0][2], mat[1][2], mat[2][2], 0); + + // Multiply x, y, z and matrix values + __m256d __mul_vecx_row0 = _mm256_mul_pd(__vecx, __mat_row0); + __m256d __mul_vecy_row1 = _mm256_mul_pd(__vecy, __mat_row1); + __m256d __mul_vecz_row2 = _mm256_mul_pd(__vecz, __mat_row2); + + // Sum up the products + __m256d __sum = _mm256_add_pd(__mul_vecx_row0, _mm256_add_pd(__mul_vecy_row1, __mul_vecz_row2)); + + // Store translation values + __m256d __translation = _mm256_set_pd(mat[0][3], mat[1][3], mat[2][3], 0); + + // Add the translation values + __m256d __final = _mm256_add_pd(__sum, __translation); + + double dfinal[4]; + + _mm256_storeu_pd(dfinal, __final); + + x = dfinal[3]; + y = dfinal[2]; + z = dfinal[1]; + + #else + Vector3 buffer = *this; + x = (mat[0][0] * buffer.x) + (mat[0][1] * buffer.y) + (mat[0][2] * buffer.z); + y = (mat[1][0] * buffer.x) + (mat[1][1] * buffer.y) + (mat[1][2] * buffer.z); + z = (mat[2][0] * buffer.x) + (mat[2][1] * buffer.y) + (mat[2][2] * buffer.z); + + // Translation + x += mat[0][3]; + y += mat[1][3]; + z += mat[2][3]; + #endif + + return; +} + +template +Vector3 Vector3::operator-() const +{ + return Vector3( + -x, + -y, + -z + ); +} + +template +void Vector3::operator=(const Vector3& other) +{ + x = other.x; + y = other.y; + z = other.z; + + return; +} + +template +void Vector3::operator=(Vector3&& other) noexcept +{ + x = std::move(other.x); + y = std::move(other.y); + z = std::move(other.z); + + return; +} + +// Slow, lame version for intcels +void Vector3::operator*=(const Matrix4x4& mat) +{ + Vector3 buffer(x, y, z); + + x = (int)((mat[0][0] * buffer.x) + (mat[0][1] * buffer.y) + (mat[0][2] * buffer.z)); + y = (int)((mat[1][0] * buffer.x) + (mat[1][1] * buffer.y) + (mat[1][2] * buffer.z)); + z = (int)((mat[2][0] * buffer.x) + (mat[2][1] * buffer.y) + (mat[2][2] * buffer.z)); + + // Translation + x += (int)mat[0][3]; + y += (int)mat[1][3]; + z += (int)mat[2][3]; + + return; +} + + + +template +bool Vector3::operator==(const Vector3& other) const +{ + return + (x == other.x) && + (y == other.y) && + (z == other.z); +} + +template +bool Vector3::operator!=(const Vector3& other) const +{ + return !operator==(other); +} + + +#include "Vector2.h" +#include "Vector4.h" +template +Vector3::operator Vector2() const +{ + return Vector2(x, y); +} + +template +Vector3::operator Vector4() const +{ + return Vector4(x, y, z, 0); +} + +template class Vector3; +template class Vector3; + +// Some handy predefines +template +const Vector3 Vector3::up(0, 1, 0); +template +const Vector3 Vector3::down(0, -1, 0); +template +const Vector3 Vector3::right(1, 0, 0); +template +const Vector3 Vector3::left(-1, 0, 0); +template +const Vector3 Vector3::forward(0, 0, 1); +template +const Vector3 Vector3::backward(0, 0, -1); +template +const Vector3 Vector3::one(1, 1, 1); +template +const Vector3 Vector3::zero(0, 0, 0); diff --git a/Eule/Vector3.h b/Eule/Vector3.h new file mode 100644 index 0000000..0d4b34a --- /dev/null +++ b/Eule/Vector3.h @@ -0,0 +1,111 @@ +#pragma once +#include +#include +#include +#include +#include "Matrix4x4.h" + +namespace Eule +{ + template class Vector2; + template class Vector4; + + /** Representation of a 3d vector. + * Contains a lot of utility methods. + */ + template + class Vector3 + { + public: + Vector3() : x{ 0 }, y{ 0 }, z{ 0 } {} + Vector3(T _x, T _y, T _z) : x{ _x }, y{ _y }, z{ _z } {} + Vector3(const Vector3& other) = default; + Vector3(Vector3&& other) noexcept = default; + + //! Will compute the dot product to another Vector3 + double DotProduct(const Vector3& other) const; + + //! Will compute the cross product to another Vector3 + Vector3 CrossProduct(const Vector3& other) const; + + //! Will compute the square magnitude + double SqrMagnitude() const; + + //! Will compute the magnitude + double Magnitude() const; + + //! Will return the normalization of this vector + [[nodiscard]] Vector3 Normalize() const; + + //! Will normalize this vector + void NormalizeSelf(); + + //! Will scale self.n by scalar.n + [[nodiscard]] Vector3 VectorScale(const Vector3& scalar) const; + + //! Will lerp itself towards other by t + void LerpSelf(const Vector3& other, double t); + + //! Will return a lerp result between this and another vector + [[nodiscard]] Vector3 Lerp(const Vector3& other, double t) const; + + //! Will compare if two vectors are similar to a certain epsilon value + [[nodiscard]] bool Similar(const Vector3& other, double epsilon = 0.00001) const; + + //! Will convert this vector to a Vector3i + [[nodiscard]] Vector3 ToInt() const; + + //! Will convert this vector to a Vector3d + [[nodiscard]] Vector3 ToDouble() const; + + T& operator[](std::size_t idx); + const T& operator[](std::size_t idx) const; + + Vector3 operator+(const Vector3& other) const; + void operator+=(const Vector3& other); + Vector3 operator-(const Vector3& other) const; + void operator-=(const Vector3& other); + Vector3 operator*(const T scale) const; + void operator*=(const T scale); + Vector3 operator/(const T scale) const; + void operator/=(const T scale); + Vector3 operator*(const Matrix4x4& mat) const; + void operator*=(const Matrix4x4& mat); + Vector3 operator-() const; + + operator Vector2() const; //! Conversion method + operator Vector4() const; //! Conversion method + + void operator=(const Vector3& other); + void operator=(Vector3&& other) noexcept; + + bool operator==(const Vector3& other) const; + bool operator!=(const Vector3& other) const; + + friend std::ostream& operator << (std::ostream& os, const Vector3& v) + { + return os << "[x: " << v.x << " y: " << v.y << " z: " << v.z << "]"; + } + friend std::wostream& operator << (std::wostream& os, const Vector3& v) + { + return os << L"[x: " << v.x << L" y: " << v.y << L" z: " << v.z << L"]"; + } + + T x; + T y; + T z; + + // Some handy predefines + static const Vector3 up; + static const Vector3 down; + static const Vector3 right; + static const Vector3 left; + static const Vector3 forward; + static const Vector3 backward; + static const Vector3 one; + static const Vector3 zero; + }; + + typedef Vector3 Vector3i; + typedef Vector3 Vector3d; +} diff --git a/Eule/Vector4.cpp b/Eule/Vector4.cpp new file mode 100644 index 0000000..850c3bb --- /dev/null +++ b/Eule/Vector4.cpp @@ -0,0 +1,809 @@ +#include "Vector4.h" +#include "Math.h" +#include + +//#define _EULE_NO_INTRINSICS_ +#ifndef _EULE_NO_INTRINSICS_ +#include +#endif + +using namespace Eule; + +/* + NOTE: + Here you will find bad, unoptimized methods for T=int. + This is because the compiler needs a method for each type in each instantiation of the template! + I can't generalize the methods when heavily optimizing for doubles. + These functions will get called VERY rarely, if ever at all, for T=int, so it's ok. + The T=int instantiation only exists to store a value-pair of two ints. Not so-much as a vector in terms of vector calculus. +*/ + +// Good, optimized chad version for doubles +double Vector4::SqrMagnitude() const +{ + return (x * x) + + (y * y) + + (z * z) + + (w * w); +} + +// Slow, lame version for intcels +double Vector4::SqrMagnitude() const +{ + int iSqrMag = x*x + y*y + z*z + w*w; + return (double)iSqrMag; +} + +template +double Vector4::Magnitude() const +{ + return sqrt(SqrMagnitude()); +} + + +Vector4 Vector4::VectorScale(const Vector4& scalar) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Load vectors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __vector_scalar = _mm256_set_pd(scalar.w, scalar.z, scalar.y, scalar.x); + + // Multiply them + __m256d __product = _mm256_mul_pd(__vector_self, __vector_scalar); + + // Retrieve result + double result[4]; + _mm256_storeu_pd(result, __product); + + // Return value + return Vector4( + result[0], + result[1], + result[2], + result[3] + ); + + #else + + return Vector4( + x * scalar.x, + y * scalar.y, + z * scalar.z, + w * scalar.w + ); + #endif +} + + +Vector4 Vector4::VectorScale(const Vector4& scalar) const +{ + return Vector4( + x * scalar.x, + y * scalar.y, + z * scalar.z, + w * scalar.w + ); +} + + + +template +Vector4 Vector4::Normalize() const +{ + Vector4 norm(x, y, z, w); + norm.NormalizeSelf(); + + return norm; +} + +// Method to normalize a Vector43d +void Vector4::NormalizeSelf() +{ + double length = Magnitude(); + + // Prevent division by 0 + if (length == 0) + { + x = 0; + y = 0; + z = 0; + w = 0; + } + else + { + #ifndef _EULE_NO_INTRINSICS_ + + // Load vector and length into registers + __m256d __vec = _mm256_set_pd(w, z, y, x); + __m256d __len = _mm256_set1_pd(length); + + // Divide + __m256d __prod = _mm256_div_pd(__vec, __len); + + // Extract and set values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + x = prod[0]; + y = prod[1]; + z = prod[2]; + w = prod[3]; + + #else + + x /= length; + y /= length; + z /= length; + w /= length; + + #endif + } + + return; +} + +// You can't normalize an int vector, ffs! +// But we need an implementation for T=int +void Vector4::NormalizeSelf() +{ + std::cerr << "Stop normalizing int-vectors!!" << std::endl; + x = 0; + y = 0; + z = 0; + w = 0; + + return; +} + + + +template +bool Vector4::Similar(const Vector4& other, double epsilon) const +{ + return + (::Math::Similar(x, other.x, epsilon)) && + (::Math::Similar(y, other.y, epsilon)) && + (::Math::Similar(z, other.z, epsilon)) && + (::Math::Similar(w, other.w, epsilon)) + ; +} + +template +Vector4 Vector4::ToInt() const +{ + return Vector4((int)x, (int)y, (int)z, (int)w); +} + +template +Vector4 Vector4::ToDouble() const +{ + return Vector4((double)x, (double)y, (double)z, (double)w); +} + +template +T& Vector4::operator[](std::size_t idx) +{ + switch (idx) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + default: + throw std::out_of_range("Array descriptor on Vector4 out of range!"); + } +} + +template +const T& Vector4::operator[](std::size_t idx) const +{ + switch (idx) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + default: + throw std::out_of_range("Array descriptor on Vector4 out of range!"); + } +} + + + +// Good, optimized chad version for doubles +void Vector4::LerpSelf(const Vector4& other, double t) +{ + const double it = 1.0 - t; // Inverse t + + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __vector_other = _mm256_set_pd(other.w, other.z, other.y, other.x); + __m256d __t = _mm256_set1_pd(t); + __m256d __it = _mm256_set1_pd(it); // Inverse t + + // Procedure: + // (__vector_self * __it) + (__vector_other * __t) + + __m256d __sum = _mm256_set1_pd(0); // this will hold the sum of the two multiplications + + __sum = _mm256_fmadd_pd(__vector_self, __it, __sum); + __sum = _mm256_fmadd_pd(__vector_other, __t, __sum); + + // Retrieve result, and apply it + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + x = sum[0]; + y = sum[1]; + z = sum[2]; + w = sum[3]; + + #else + + x = it * x + t * other.x; + y = it * y + t * other.y; + z = it * z + t * other.z; + w = it * w + t * other.w; + + #endif + + return; +} + + + +// Slow, lame version for intcels +void Vector4::LerpSelf(const Vector4& other, double t) +{ + const double it = 1.0 - t; + + x = (int)(it * (double)x + t * (double)other.x); + y = (int)(it * (double)y + t * (double)other.y); + z = (int)(it * (double)z + t * (double)other.z); + w = (int)(it * (double)w + t * (double)other.w); + + return; +} + +Vector4 Vector4::Lerp(const Vector4& other, double t) const +{ + Vector4d copy(*this); + copy.LerpSelf(other, t); + + return copy; +} + +Vector4 Vector4::Lerp(const Vector4& other, double t) const +{ + Vector4d copy(this->ToDouble()); + copy.LerpSelf(other.ToDouble(), t); + + return copy; +} + + + +Vector4 Vector4::operator+(const Vector4& other) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __vector_other = _mm256_set_pd(other.w, other.z, other.y, other.x); + + // Add the components + __m256d __sum = _mm256_add_pd(__vector_self, __vector_other); + + // Retrieve and return these values + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + return Vector4( + sum[0], + sum[1], + sum[2], + sum[3] + ); + + #else + + return Vector4( + x + other.x, + y + other.y, + z + other.z, + w + other.w + ); + #endif +} + +template +Vector4 Vector4::operator+(const Vector4& other) const +{ + return Vector4( + x + other.x, + y + other.y, + z + other.z, + w + other.w + ); +} + + + +void Vector4::operator+=(const Vector4& other) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __vector_other = _mm256_set_pd(other.w, other.z, other.y, other.x); + + // Add the components + __m256d __sum = _mm256_add_pd(__vector_self, __vector_other); + + // Retrieve and apply these values + double sum[4]; + _mm256_storeu_pd(sum, __sum); + + x = sum[0]; + y = sum[1]; + z = sum[2]; + w = sum[3]; + + #else + + x += other.x; + y += other.y; + z += other.z; + w += other.w; + + #endif + + return; +} + +template +void Vector4::operator+=(const Vector4& other) +{ + x += other.x; + y += other.y; + z += other.z; + w += other.w; + return; +} + + + +Vector4 Vector4::operator-(const Vector4& other) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __vector_other = _mm256_set_pd(other.w, other.z, other.y, other.x); + + // Subtract the components + __m256d __diff = _mm256_sub_pd(__vector_self, __vector_other); + + // Retrieve and return these values + double diff[4]; + _mm256_storeu_pd(diff, __diff); + + return Vector4( + diff[0], + diff[1], + diff[2], + diff[3] + ); + + #else + + return Vector4( + x - other.x, + y - other.y, + z - other.z, + w - other.w + ); + #endif +} + +template +Vector4 Vector4::operator-(const Vector4& other) const +{ + return Vector4( + x - other.x, + y - other.y, + z - other.z, + w - other.w + ); +} + + + +void Vector4::operator-=(const Vector4& other) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __vector_other = _mm256_set_pd(other.w, other.z, other.y, other.x); + + // Subtract the components + __m256d __diff = _mm256_sub_pd(__vector_self, __vector_other); + + // Retrieve and apply these values + double diff[4]; + _mm256_storeu_pd(diff, __diff); + + x = diff[0]; + y = diff[1]; + z = diff[2]; + w = diff[3]; + + #else + + x -= other.x; + y -= other.y; + z -= other.z; + w -= other.w; + + #endif + + return; +} + +template +void Vector4::operator-=(const Vector4& other) +{ + x -= other.x; + y -= other.y; + z -= other.z; + w -= other.w; + return; +} + + + +Vector4 Vector4::operator*(const double scale) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Multiply the components + __m256d __prod = _mm256_mul_pd(__vector_self, __scalar); + + // Retrieve and return these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + return Vector4( + prod[0], + prod[1], + prod[2], + prod[3] + ); + + #else + + return Vector4( + x * scale, + y * scale, + z * scale, + w * scale + ); + + #endif +} + +template +Vector4 Vector4::operator*(const T scale) const +{ + return Vector4( + x * scale, + y * scale, + z * scale, + w * scale + ); +} + + + +void Vector4::operator*=(const double scale) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Multiply the components + __m256d __prod = _mm256_mul_pd(__vector_self, __scalar); + + // Retrieve and apply these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + x = prod[0]; + y = prod[1]; + z = prod[2]; + w = prod[3]; + + #else + + x *= scale; + y *= scale; + z *= scale; + w *= scale; + + #endif + + return; +} + +template +void Vector4::operator*=(const T scale) +{ + x *= scale; + y *= scale; + z *= scale; + w *= scale; + return; +} + + + +Vector4 Vector4::operator/(const double scale) const +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Divide the components + __m256d __prod = _mm256_div_pd(__vector_self, __scalar); + + // Retrieve and return these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + return Vector4( + prod[0], + prod[1], + prod[2], + prod[3] + ); + + #else + + return Vector4( + x / scale, + y / scale, + z / scale, + w / scale + ); + + #endif +} + +template +Vector4 Vector4::operator/(const T scale) const +{ + return Vector4( + x / scale, + y / scale, + z / scale, + w / scale + ); +} + + + +void Vector4::operator/=(const double scale) +{ + #ifndef _EULE_NO_INTRINSICS_ + + // Move vector components and factors into registers + __m256d __vector_self = _mm256_set_pd(w, z, y, x); + __m256d __scalar = _mm256_set1_pd(scale); + + // Divide the components + __m256d __prod = _mm256_div_pd(__vector_self, __scalar); + + // Retrieve and apply these values + double prod[4]; + _mm256_storeu_pd(prod, __prod); + + x = prod[0]; + y = prod[1]; + z = prod[2]; + w = prod[3]; + + #else + + x /= scale; + y /= scale; + z /= scale; + w /= scale; + + #endif + return; +} + +template +void Vector4::operator/=(const T scale) +{ + x /= scale; + y /= scale; + z /= scale; + w /= scale; + return; +} + + + +template +bool Vector4::operator==(const Vector4& other) const +{ + return + (x == other.x) && + (y == other.y) && + (z == other.z) && + (w == other.w); +} + + + +// Good, optimized chad version for doubles +Vector4 Vector4::operator*(const Matrix4x4& mat) const +{ + Vector4 newVec; + + newVec.x = (mat[0][0] * x) + (mat[0][1] * y) + (mat[0][2] * z) + (mat[0][3] * w); + newVec.y = (mat[1][0] * x) + (mat[1][1] * y) + (mat[1][2] * z) + (mat[1][3] * w); + newVec.z = (mat[2][0] * x) + (mat[2][1] * y) + (mat[2][2] * z) + (mat[2][3] * w); + newVec.w = (mat[3][0] * x) + (mat[3][1] * y) + (mat[3][2] * z) + (mat[3][3] * w); + + return newVec; +} + +// Slow, lame version for intcels +Vector4 Vector4::operator*(const Matrix4x4& mat) const +{ + Vector4 newVec; + + newVec.x = (mat[0][0] * x) + (mat[0][1] * y) + (mat[0][2] * z) + (mat[0][3] * w); + newVec.y = (mat[1][0] * x) + (mat[1][1] * y) + (mat[1][2] * z) + (mat[1][3] * w); + newVec.z = (mat[2][0] * x) + (mat[2][1] * y) + (mat[2][2] * z) + (mat[2][3] * w); + newVec.w = (mat[3][0] * x) + (mat[3][1] * y) + (mat[3][2] * z) + (mat[3][3] * w); + + return Vector4( + (int)newVec.x, + (int)newVec.y, + (int)newVec.z, + (int)newVec.w + ); +} + + + +// Good, optimized chad version for doubles +void Vector4::operator*=(const Matrix4x4& mat) +{ + Vector4 buffer = *this; + + // Should this still be reversed...? like, instead of mat[x][y], use mat[y][m] + // idk right now. check that if something doesn't work + x = (mat[0][0] * buffer.x) + (mat[0][1] * buffer.y) + (mat[0][2] * buffer.z) + (mat[0][3] * buffer.w); + y = (mat[1][0] * buffer.x) + (mat[1][1] * buffer.y) + (mat[1][2] * buffer.z) + (mat[1][3] * buffer.w); + z = (mat[2][0] * buffer.x) + (mat[2][1] * buffer.y) + (mat[2][2] * buffer.z) + (mat[2][3] * buffer.w); + w = (mat[3][0] * buffer.x) + (mat[3][1] * buffer.y) + (mat[3][2] * buffer.z) + (mat[3][3] * buffer.w); + + return; +} + +template +Vector4 Vector4::operator-() const +{ + return Vector4( + -x, + -y, + -z, + -w + ); +} + +template +void Vector4::operator=(const Vector4& other) +{ + x = other.x; + y = other.y; + z = other.z; + w = other.w; + + return; +} + +template +void Vector4::operator=(Vector4&& other) noexcept +{ + x = std::move(other.x); + y = std::move(other.y); + z = std::move(other.z); + w = std::move(other.w); + + return; +} + +// Slow, lame version for intcels +void Vector4::operator*=(const Matrix4x4& mat) +{ + Vector4 buffer(x, y, z, w); + + // Should this still be reversed...? like, instead of mat[x][y], use mat[y][m] + // idk right now. check that if something doesn't work + x = (int)((mat[0][0] * buffer.x) + (mat[0][1] * buffer.y) + (mat[0][2] * buffer.z) + (mat[0][3] * buffer.w)); + y = (int)((mat[1][0] * buffer.x) + (mat[1][1] * buffer.y) + (mat[1][2] * buffer.z) + (mat[1][3] * buffer.w)); + z = (int)((mat[2][0] * buffer.x) + (mat[2][1] * buffer.y) + (mat[2][2] * buffer.z) + (mat[2][3] * buffer.w)); + w = (int)((mat[3][0] * buffer.x) + (mat[3][1] * buffer.y) + (mat[3][2] * buffer.z) + (mat[3][3] * buffer.w)); + + return; +} + +template +bool Vector4::operator!=(const Vector4& other) const +{ + return !operator==(other); +} + +#include "Vector2.h" +#include "Vector3.h" +template +Vector4::operator Vector2() const +{ + return Vector2(x, y); +} + +template +Vector4::operator Vector3() const +{ + return Vector3(x, y, z); +} + +template class Vector4; +template class Vector4; + +// Some handy predefines +template +const Vector4 Vector4::up(0, 1, 0, 0); +template +const Vector4 Vector4::down(0, -1, 0, 0); +template +const Vector4 Vector4::right(1, 0, 0, 0); +template +const Vector4 Vector4::left(-1, 0, 0, 0); +template +const Vector4 Vector4::forward(1, 0, 0, 0); +template +const Vector4 Vector4::backward(-1, 0, 0, 0); +template +const Vector4 Vector4::future(0, 0, 0, 1); +template +const Vector4 Vector4::past(0, 0, 0, -1); +template +const Vector4 Vector4::one(1, 1, 1, 1); +template +const Vector4 Vector4::zero(0, 0, 0, 0); diff --git a/Eule/Vector4.h b/Eule/Vector4.h new file mode 100644 index 0000000..0f34b4f --- /dev/null +++ b/Eule/Vector4.h @@ -0,0 +1,108 @@ +#pragma once +#include +#include +#include +#include +#include "Matrix4x4.h" + +namespace Eule +{ + template class Vector2; + template class Vector3; + + /** Representation of a 4d vector. + * Contains a lot of utility methods. + */ + template + class Vector4 + { + public: + Vector4() : x{ 0 }, y{ 0 }, z{ 0 }, w{ 0 } {} + Vector4(T _x, T _y, T _z, T _w) : x{ _x }, y{ _y }, z{ _z }, w{ _w } {} + Vector4(const Vector4& other) = default; + Vector4(Vector4&& other) noexcept = default; + + //! Will compute the square magnitude + double SqrMagnitude() const; + + //! Will compute the magnitude + double Magnitude() const; + + //! Will return the normalization of this vector + [[nodiscard]] Vector4 Normalize() const; + + //! Will normalize this vector + void NormalizeSelf(); + + //! Will scale self.n by scalar.n + [[nodiscard]] Vector4 VectorScale(const Vector4& scalar) const; + + //! Will lerp itself towards other by t + void LerpSelf(const Vector4& other, double t); + + //! Will return a lerp result between this and another vector + [[nodiscard]] Vector4 Lerp(const Vector4& other, double t) const; + + //! Will compare if two vectors are similar to a certain epsilon value + [[nodiscard]] bool Similar(const Vector4& other, double epsilon = 0.00001) const; + + //! Will convert this vector to a Vector4i + [[nodiscard]] Vector4 ToInt() const; + + //! Will convert this vector to a Vector4d + [[nodiscard]] Vector4 ToDouble() const; + + T& operator[](std::size_t idx); + const T& operator[](std::size_t idx) const; + + Vector4 operator+(const Vector4& other) const; + void operator+=(const Vector4& other); + Vector4 operator-(const Vector4& other) const; + void operator-=(const Vector4& other); + Vector4 operator*(const T scale) const; + void operator*=(const T scale); + Vector4 operator/(const T scale) const; + void operator/=(const T scale); + Vector4 operator*(const Matrix4x4& mat) const; + void operator*=(const Matrix4x4& mat); + Vector4 operator-() const; + + operator Vector2() const; //! Conversion method + operator Vector3() const; //! Conversion method + + void operator=(const Vector4& other); + void operator=(Vector4&& other) noexcept; + + bool operator==(const Vector4& other) const; + bool operator!=(const Vector4& other) const; + + friend std::ostream& operator << (std::ostream& os, const Vector4& v) + { + return os << "[x: " << v.x << " y: " << v.y << " z: " << v.z << " w: " << v.w << "]"; + } + friend std::wostream& operator << (std::wostream& os, const Vector4& v) + { + return os << L"[x: " << v.x << L" y: " << v.y << L" z: " << v.z << L" w: " << v.w << L"]"; + } + + T x; + T y; + T z; + T w; + + // Some handy predefines + static const Vector4 up; + static const Vector4 down; + static const Vector4 right; + static const Vector4 left; + static const Vector4 forward; + static const Vector4 backward; + static const Vector4 future; + static const Vector4 past; + static const Vector4 one; + static const Vector4 zero; + }; + + typedef Vector4 Vector4i; + typedef Vector4 Vector4d; +} diff --git a/Test/Math_Abs.cpp b/Test/Math_Abs.cpp new file mode 100644 index 0000000..6a885eb --- /dev/null +++ b/Test/Math_Abs.cpp @@ -0,0 +1,39 @@ +#include "CppUnitTest.h" +#include "../Eule/Math.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +/** Equivalence classes: +* -- value > 0 +* -- value < 0 +* -- value == 0 +*/ + +namespace _Math +{ + TEST_CLASS(_Abs) + { + public: + // Checks with a positive input + TEST_METHOD(Positive_Value) + { + Assert::AreEqual(45.0, Math::Abs(45.0)); + return; + } + + // Checks with a negative input + TEST_METHOD(Negative_Value) + { + Assert::AreEqual(45.0, Math::Abs(-45.0)); + return; + } + + // Checks with a zero input + TEST_METHOD(Zero_Value) + { + Assert::AreEqual(0.0, Math::Abs(0.0)); + return; + } + }; +} diff --git a/Test/Math_Clamp.cpp b/Test/Math_Clamp.cpp new file mode 100644 index 0000000..2fe71c1 --- /dev/null +++ b/Test/Math_Clamp.cpp @@ -0,0 +1,87 @@ +#include "CppUnitTest.h" +#include "../Eule/Math.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +/** Equivalence classes: +* -- min < v < max -> v +* -- v < min < max -> min +* -- min < max < v -> max +* -- v == min < max -> min +* -- min < v == max -> max +* -- v < max == min -> max +* -- max == min < v -> max +* -- min == v == max -> max +* -- max < v < min -> min +*/ + +namespace _Math +{ + TEST_CLASS(_Clamp) + { + public: + // min < v < max -> v + TEST_METHOD(min_lt_v_lt_max) + { + Assert::AreEqual(6.0, Math::Clamp(6.0, 4.0, 9.0)); + return; + } + + // v < min < max -> min + TEST_METHOD(v_lt_min_max) + { + Assert::AreEqual(6.0, Math::Clamp(4.0, 6.0, 9.0)); + return; + } + + // min < max < v -> max + TEST_METHOD(min_lt_max_lt_v) + { + Assert::AreEqual(9.0, Math::Clamp(12.0, 6.0, 9.0)); + return; + } + + // v == min < max -> min + TEST_METHOD(v_eq_min_lt_max) + { + Assert::AreEqual(6.0, Math::Clamp(6.0, 6.0, 9.0)); + return; + } + + // min < v == max -> max + TEST_METHOD(min_lt_v_eq_max) + { + Assert::AreEqual(9.0, Math::Clamp(9.0, 6.0, 9.0)); + return; + } + + // v < max == min -> max + TEST_METHOD(v_lt_max_eq_min) + { + Assert::AreEqual(9.0, Math::Clamp(9.0, 6.0, 9.0)); + return; + } + + // max == min < v -> max + TEST_METHOD(max_eq_min_lt_v) + { + Assert::AreEqual(9.0, Math::Clamp(15.0, 9.0, 9.0)); + return; + } + + // min == v == max -> max + TEST_METHOD(min_eq_v_eq_max) + { + Assert::AreEqual(15.0, Math::Clamp(15.0, 15.0, 15.0)); + return; + } + + // max < v < min -> min + TEST_METHOD(max_lt_v_lt_min) + { + Assert::AreEqual(7.0, Math::Clamp(4.0, 7.0, 3.0)); + return; + } + }; +} diff --git a/Test/Math_Lerp.cpp b/Test/Math_Lerp.cpp new file mode 100644 index 0000000..5ae0ab3 --- /dev/null +++ b/Test/Math_Lerp.cpp @@ -0,0 +1,115 @@ +#include "CppUnitTest.h" +#include "../Eule/Math.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +/** Equivalence classes: +* -- a < b, 0 < t < 1 -> a < result < b +* -- a > b, 0 < t < 1 -> b < result < a +* -- a == b, 0 < t < 1 -> result == b +* +* -- a < b, t < 0 -> result < a +* -- a > b, t < 0 -> result > a +* -- a == b, t < 0 -> result == b +* +* -- a < b, t > 1 -> result > b +* -- a > b, t > 1 -> result < b +* -- a == b, t > 1 -> result == b +* +* -- a == 0 +* -- b == 0 +* -- a == b == 0 +*/ + +namespace _Math +{ + TEST_CLASS(_Lerp) + { + public: + // -- a < b, 0 < t < 1 -> a < result < b + TEST_METHOD(a_lt_b___0_lt_t_lt_1) + { + Assert::AreEqual(1.75, Math::Lerp(1, 2, 0.75)); + return; + } + + // -- a > b, 0 < t < 1 -> b < result < a + TEST_METHOD(a_gt_b___0_lt_t_lt_1) + { + Assert::AreEqual(1.25, Math::Lerp(2, 1, 0.75)); + return; + } + + // -- a == b, 0 < t < 1 -> result == b + TEST_METHOD(a_eq_b___0_lt_t_lt_1) + { + Assert::AreEqual(2.0, Math::Lerp(2, 2, 0.75)); + return; + } + + // -- a < b, t < 0 -> result < a + TEST_METHOD(a_lt_b___t_lt_0) + { + Assert::AreEqual(0.25, Math::Lerp(1, 2, -0.75)); + return; + } + + // -- a > b, t < 0 -> result > a + TEST_METHOD(a_gt_b___t_lt_0) + { + Assert::AreEqual(2.75, Math::Lerp(2, 1, -0.75)); + return; + } + + // -- a == b, t < 0 -> result == b + TEST_METHOD(a_eq_b___t_lt_0) + { + Assert::AreEqual(2.0, Math::Lerp(2, 2, -0.75)); + return; + } + + // -- a < b, t > 1 -> result > b + TEST_METHOD(a_lt_b___t_gt_1) + { + Assert::AreEqual(2.5, Math::Lerp(1, 2, 1.5)); + return; + } + + // -- a > b, t > 1 -> result < b + TEST_METHOD(a_gt_b___t_gt_1) + { + Assert::AreEqual(0.5, Math::Lerp(2, 1, 1.5)); + return; + } + + // -- a == b, t > 1 -> result == b + TEST_METHOD(a_eq_b___t_gt_1) + { + Assert::AreEqual(1.0, Math::Lerp(1, 1, 1.5)); + return; + } + + // -- a == 0 + TEST_METHOD(a_eq_0) + { + Assert::AreEqual(2.25, Math::Lerp(0, 3, 0.75)); + return; + } + + // -- b == 0 + TEST_METHOD(b_eq_0) + { + Assert::AreEqual(0.75, Math::Lerp(3, 0, 0.75)); + return; + } + + // -- a == b == 0 + TEST_METHOD(a_eq_b_eq_0) + { + Assert::AreEqual(0.0, Math::Lerp(0, 0, 0.75)); + return; + } + + }; +} diff --git a/Test/Math_Max.cpp b/Test/Math_Max.cpp new file mode 100644 index 0000000..34dc011 --- /dev/null +++ b/Test/Math_Max.cpp @@ -0,0 +1,40 @@ +#include "CppUnitTest.h" +#include "../Eule/Math.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +/** Equivalence classes: +* -- a < b +* -- a > b +* -- a == b +*/ + +namespace _Math +{ + TEST_CLASS(_Max) + { + public: + // a < b + TEST_METHOD(a_lt_b) + { + Assert::AreEqual(12.0, Math::Max(4.0, 12.0)); + return; + } + + // a > b + TEST_METHOD(a_gt_b) + { + Assert::AreEqual(12.0, Math::Max(12.0, 4.0)); + return; + } + + // a == b + TEST_METHOD(a_eq_b) + { + Assert::AreEqual(9.0, Math::Max(9.0, 9.0)); + return; + } + + }; +} diff --git a/Test/Math_Min.cpp b/Test/Math_Min.cpp new file mode 100644 index 0000000..55315ca --- /dev/null +++ b/Test/Math_Min.cpp @@ -0,0 +1,52 @@ +#include "CppUnitTest.h" +#include "../Eule/Math.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +/** Equivalence classes: +* -- min < v < max +* -- v < min < max +* -- min < max < v +* -- v == min < max +* -- min < v == max +* -- v < max == min +* -- max == min < v +* -- max == min == v +* -- max < v < min +*/ + +/** Equivalence classes: +* -- a < b +* -- a > b +* -- a == b +*/ + +namespace _Math +{ + TEST_CLASS(_Min) + { + public: + // a < b + TEST_METHOD(a_lt_b) + { + Assert::AreEqual(4.0, Math::Min(4.0, 9.0)); + return; + } + + // a > b + TEST_METHOD(a_gt_b) + { + Assert::AreEqual(4.0, Math::Min(9.0, 4.0)); + return; + } + + // a == b + TEST_METHOD(a_eq_b) + { + Assert::AreEqual(9.0, Math::Min(9.0, 9.0)); + return; + } + + }; +} diff --git a/Test/Math_Random.cpp b/Test/Math_Random.cpp new file mode 100644 index 0000000..2a08fd2 --- /dev/null +++ b/Test/Math_Random.cpp @@ -0,0 +1,26 @@ +#include "CppUnitTest.h" +#include "../Eule/Math.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace _Math +{ + TEST_CLASS(_Random) + { + public: + // Checks that all values are always 0 <= v <= 1 + TEST_METHOD(Always_Satisfies_0_lt_v_lt_1) + { + // Test 1000 random values + for (std::size_t i = 0; i < 1e3; i++) + { + const double rnd = Math::Random(); + Assert::IsTrue(rnd >= 0.0, L"rnd < 0"); + Assert::IsTrue(rnd <= 1.0, L"rnd > 1"); + } + + return; + } + }; +} diff --git a/Test/Math_RandomIntRange.cpp b/Test/Math_RandomIntRange.cpp new file mode 100644 index 0000000..2b32d37 --- /dev/null +++ b/Test/Math_RandomIntRange.cpp @@ -0,0 +1,100 @@ +#include "CppUnitTest.h" +#include "../_TestingUtilities/Testutil.h" +#include "../Eule/Math.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace _Math +{ + TEST_CLASS(_RandomIntRange) + { + public: + // Checks that a random integer is never outside the specification, two positive values + TEST_METHOD(Never_Outside_Specification__pos__pos) + { + // Test 1000 random integers + for (std::size_t i = 0; i < 1000; i++) + { + int rnd = Math::RandomIntRange(49, 99); + + Assert::IsTrue(rnd >= 49, L"rnd too small"); + Assert::IsTrue(rnd <= 99, L"rnd too big"); + } + + return; + } + + // Checks that a random integer is never outside the specification, negative minimum + TEST_METHOD(Never_Outside_Specification__neg__pos) + { + // Test 1000 random integers + for (std::size_t i = 0; i < 1000; i++) + { + int rnd = Math::RandomIntRange(-39, 99); + + Assert::IsTrue(rnd >= -39, L"rnd too small"); + Assert::IsTrue(rnd <= 99, L"rnd too big"); + } + + return; + } + + // Checks that a random integer is never outside the specification, two negative values + TEST_METHOD(Never_Outside_Specification__neg__neg) + { + // Test 1000 random integers + for (std::size_t i = 0; i < 1000; i++) + { + int rnd = Math::RandomIntRange(-39, -10); + + Assert::IsTrue(rnd >= -39, L"rnd too small"); + Assert::IsTrue(rnd <= -10, L"rnd too big"); + } + + return; + } + + // Checks that the random intrange method also returns the supplied limits + TEST_METHOD(Inclusivity) + { + // Test 1000 random integers + // The chance that any number [0,9] will not drop at least once is basically 0 + + std::array foundDigits; + foundDigits.fill(false); + + for (std::size_t i = 0; i < 1000; i++) + { + int randomVal = Math::RandomIntRange(0, 9); + foundDigits[randomVal] = true; + } + + // Check that each value has been rolled at least once + for (const bool& b : foundDigits) + Assert::IsTrue(b); + + return; + } + + // Checks that the produced integer distribution shows a big standard deviation + TEST_METHOD(Big_Standard_Deviation) + { + // Setup + std::vector rands; + rands.resize(1000); + + // Exercise + // Create 1000 random values + std::generate_n(rands.data(), rands.size(), []()->int { return Math::RandomIntRange(100, (int)4e9); }); + + // Verify + const double stddev = Testutil::Stddev(rands); + Assert::IsTrue(stddev >= 1000000, (std::wstringstream() << stddev).str().c_str()); + + return; + } + }; +} diff --git a/Test/Math_RandomInteger.cpp b/Test/Math_RandomInteger.cpp new file mode 100644 index 0000000..ee60094 --- /dev/null +++ b/Test/Math_RandomInteger.cpp @@ -0,0 +1,51 @@ +#include "CppUnitTest.h" +#include "../_TestingUtilities/Testutil.h" +#include "../Eule/Math.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace _Math +{ + TEST_CLASS(_RandomInteger) + { + public: + // Checks that the produced unsigned-integer distribution shows a big standard deviation + TEST_METHOD(Uint_Big_Standard_Deviation) + { + // Setup + std::vector rands; + rands.resize(1000); + + // Exercise + // Create 1000 random values + std::generate_n(rands.data(), rands.size(), []()->unsigned int { return Math::RandomUint(); }); + + // Verify + const double stddev = Testutil::Stddev(rands); + Assert::IsTrue(stddev >= 1000000, (std::wstringstream() << stddev).str().c_str()); + + return; + } + + // Checks that the produced integer distribution shows a big standard deviation + TEST_METHOD(Int_Big_Standard_Deviation) + { + // Setup + std::vector rands; + rands.resize(1000); + + // Exercise + // Create 1000 random values + std::generate_n(rands.data(), rands.size(), []()->int { return Math::RandomInt(); }); + + // Verify + const double stddev = Testutil::Stddev(rands); + Assert::IsTrue(stddev >= 1000000, (std::wstringstream() << stddev).str().c_str()); + + return; + } + }; +} diff --git a/Test/Math_Similar.cpp b/Test/Math_Similar.cpp new file mode 100644 index 0000000..2a6908b --- /dev/null +++ b/Test/Math_Similar.cpp @@ -0,0 +1,61 @@ +#include "CppUnitTest.h" +#include "../Eule/Math.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace _Math +{ + TEST_CLASS(_Similar) + { + public: + // Checks that the similar function works with an exact comparison -> true + TEST_METHOD(Exact_Comparison_True) + { + Assert::IsTrue(Math::Similar(100, 100, 0)); + return; + } + + // Checks that the similar function works with an exact comparison -> false + TEST_METHOD(Exact_Comparison_False) + { + Assert::IsFalse(Math::Similar(100, 100.001, 0)); + return; + } + + // Checks that the similar function works with an exact comparison -> false + TEST_METHOD(Exact_Comparison_False2) + { + Assert::IsFalse(Math::Similar(100, 99.999, 0)); + return; + } + + // Checks that the similar function works with a loose comparison -> true + TEST_METHOD(Loose_Comparison_True) + { + Assert::IsTrue(Math::Similar(100, 100.001, 0.01)); + return; + } + + // Checks that the similar function works with a loose comparison -> true + TEST_METHOD(Loose_Comparison_True2) + { + Assert::IsTrue(Math::Similar(100, 99.999, 0.01)); + return; + } + + // Checks that the similar function works with a loose comparison -> false + TEST_METHOD(Loose_Comparison_False) + { + Assert::IsFalse(Math::Similar(100, 100.1, 0.01)); + return; + } + + // Checks that the similar function works with a loose comparison -> false + TEST_METHOD(Loose_Comparison_False2) + { + Assert::IsFalse(Math::Similar(100, 99.9, 0.01)); + return; + } + }; +} diff --git a/Test/Math__Oscillate.cpp b/Test/Math__Oscillate.cpp new file mode 100644 index 0000000..9268e6e --- /dev/null +++ b/Test/Math__Oscillate.cpp @@ -0,0 +1,231 @@ +#include "CppUnitTest.h" +#include "../_TestingUtilities/Testutil.h" +#include "../Eule/Math.h" +#include "../Eule/Constants.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace _Math +{ + TEST_CLASS(_Oscillate) + { + public: + + // Checks that an oscillation of speed 1 between -1 and 1 is just equal to sin(counter*pi-pi/2) + TEST_METHOD(Oracle_Sin) + { + // Test 1000 random floats + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + const double rnd = Math::RandomRange(-1000, 1000); + + // Exercise + const double result = Math::Oscillate(-1, 1, rnd, 1); + + // Verify + const double expected = sin(rnd * PI - HALF_PI); + Assert::IsTrue(Math::Similar(expected, result)); + } + + return; + } + + // Tests that the result is a, if the counter is 0 or a whole, even integer + TEST_METHOD(Returns_a_For_Counter_0) + { + // Test 1000 random floats + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + const double a = Math::RandomRange(-1000, 1000); + const double b = Math::RandomRange(-1000, 1000); + const int even = Math::RandomIntRange(-1000, 1000) & ~1; + + // Exercise + const double result = Math::Oscillate(a, b, even, 1); + + // Verify + const double expected = a; + + std::wstringstream wss; + wss << std::endl + << "a: " << a << std::endl + << "b: " << b << std::endl + << "expected: " << expected << std::endl + << "result: " << result << std::endl + << std::endl; + + Assert::IsTrue(Math::Similar(expected, result), wss.str().c_str()); + } + } + + // Tests that the result is b, if the counter is a whole, uneven integer + TEST_METHOD(Returns_b_For_Uneven_Whole_Counter) + { + // Test 1000 random floats + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + const double a = Math::RandomRange(-1000, 1000); + const double b = Math::RandomRange(-1000, 1000); + const int uneven = Math::RandomIntRange(-1000, 1000) | 1; + + // Exercise + const double result = Math::Oscillate(a, b, uneven, 1); + + // Verify + const double expected = b; + Assert::IsTrue(Math::Similar(expected, result)); + } + } + + // Tests that the result is (a+b)/2, when counter satisfies (int)x + 0.5 + TEST_METHOD(Returns_ab_mean_for_intx_plus_0p5) + { + // Test 1000 random floats + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + const double a = Math::RandomRange(-1000, 1000); + const double b = Math::RandomRange(-1000, 1000); + const int anInt = Math::RandomIntRange(-1000, 1000); + + // Exercise + const double result = Math::Oscillate(a, b, anInt + 0.5, 1); + + // Verify + const double expected = (a+b) / 2.0; + + std::wstringstream wss; + wss << std::endl + << "a: " << a << std::endl + << "b: " << b << std::endl + << "expected: " << expected << std::endl + << "result: " << result << std::endl + << std::endl; + Assert::IsTrue(Math::Similar(expected, result), wss.str().c_str()); + } + } + + // Tests that the result is (3a+b)/4, when counter satisfies 2(int)x + 0.25 + TEST_METHOD(Returns_3ab_mean_for_intx_plus_0p25_counterbase_even) + { + // Test 1000 random floats + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + const double a = Math::RandomRange(-1, 1); + const double b = Math::RandomRange(-1, 1); + const int even = Math::RandomIntRange(-1000, 1000) & ~1; + + // Exercise + const double result = Math::Oscillate(a, b, even + 0.25, 1); + + // Verify + const double expected = (3*a + b) / 4.0; + + std::wstringstream wss; + wss << std::endl + << "a: " << a << std::endl + << "b: " << b << std::endl + << "expected: " << expected << std::endl + << "result: " << result << std::endl + << std::endl; + + // Oscillate is not linear, we just want a really rough approximation + Assert::IsTrue(Math::Similar(expected, result, 0.4), wss.str().c_str()); + } + } + + // Tests that the result is (a+3b)/4, when counter satisfies 2(int)x + 0.75 + TEST_METHOD(Returns_a3b_mean_for_intx_plus_0p75_counterbase_even) + { + // Test 1000 random floats + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + const double a = Math::RandomRange(-1, 1); + const double b = Math::RandomRange(-1, 1); + const int even = Math::RandomIntRange(-1000, 1000) & ~1; + + // Exercise + const double result = Math::Oscillate(a, b, even + 0.75, 1); + + // Verify + const double expected = (a + 3*b) / 4.0; + + // Oscillate is not linear, we just want a really rough approximation + Assert::IsTrue(Math::Similar(expected, result, 0.4)); // Oscillate is not linear + } + } + + // Tests that the result is (a+3b)/4, when counter satisfies 2(int)x+1 + 0.25 + TEST_METHOD(Returns_3ab_mean_for_intx_plus_0p25_counterbase_uneven) + { + // Test 1000 random floats + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + const double a = Math::RandomRange(-1, 1); + const double b = Math::RandomRange(-1, 1); + const int uneven = Math::RandomIntRange(-1000, 1000) | 1; + + // Exercise + const double result = Math::Oscillate(a, b, uneven + 0.25, 1); + + // Verify + const double expected = (a + 3*b) / 4.0; + + // Oscillate is not linear, we just want a really rough approximation + Assert::IsTrue(Math::Similar(expected, result, 0.4)); + } + } + + // Tests that the result is (3a+b)/4, when counter satisfies 2(int)x+1 + 0.75 + TEST_METHOD(Returns_a3b_mean_for_intx_plus_0p75_counterbase_uneven) + { + // Test 1000 random floats + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + const double a = Math::RandomRange(-1, 1); + const double b = Math::RandomRange(-1, 1); + const int uneven = Math::RandomIntRange(-1000, 1000) | 1; + + // Exercise + const double result = Math::Oscillate(a, b, uneven + 0.75, 1); + + // Verify + const double expected = (3*a + b) / 4.0; + + // Oscillate is not linear, we just want a really rough approximation + Assert::IsTrue(Math::Similar(expected, result, 0.4)); // Oscillate is not linear + } + } + + // Tests that doubling the speed will double the frequency + TEST_METHOD(Doubling_Speed_Doubles_Frequency) + { + // Test 1000 random floats + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + const double a = Math::RandomRange(-1000, 1000); + const double b = Math::RandomRange(-1000, 1000); + + // Exercise + const double result = Math::Oscillate(a, b, 0.5, 2); + + // Verify + const double expected = b; + Assert::IsTrue(Math::Similar(expected, result)); + } + + return; + } + }; +} diff --git a/Test/Math__RandomRange.cpp b/Test/Math__RandomRange.cpp new file mode 100644 index 0000000..cd5f957 --- /dev/null +++ b/Test/Math__RandomRange.cpp @@ -0,0 +1,79 @@ +#include "CppUnitTest.h" +#include "../_TestingUtilities/Testutil.h" +#include "../Eule/Math.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace _Math +{ + TEST_CLASS(_RandomRange) + { + public: + + // Checks that a random double is never outside the specification, two positive values + TEST_METHOD(Random_Doublerange_Never_Outside_Specification__pos__pos) + { + // Test 1000 random integers + for (std::size_t i = 0; i < 1000; i++) + { + double rnd = Math::RandomRange(49.0, 99.0); + + Assert::IsTrue(rnd >= 49.0, L"rnd too small"); + Assert::IsTrue(rnd <= 99.0, L"rnd too big"); + } + + return; + } + + // Checks that a random double is never outside the specification, negative minimum + TEST_METHOD(Random_Doublerange_Never_Outside_Specification__neg__pos) + { + // Test 1000 random integers + for (std::size_t i = 0; i < 1000; i++) + { + double rnd = Math::RandomRange(-39.0, 99.0); + + Assert::IsTrue(rnd >= -39.0, L"rnd too small"); + Assert::IsTrue(rnd <= 99.0, L"rnd too big"); + } + + return; + } + + // Checks that a random double is never outside the specification, two negative values + TEST_METHOD(Random_Doublerange_Never_Outside_Specification__neg__neg) + { + // Test 1000 random integers + for (std::size_t i = 0; i < 1000; i++) + { + double rnd = Math::RandomRange(-39.0, -10.0); + + Assert::IsTrue(rnd >= -39.0, L"rnd too small"); + Assert::IsTrue(rnd <= -10.0, L"rnd too big"); + } + + return; + } + + // Checks that the produced double-precision floating point distribution shows a big standard deviation + TEST_METHOD(Double_Big_Standard_Deviation) + { + // Setup + std::vector rands; + rands.resize(100); + + // Exercise + // Create 1000 random values + std::generate_n(rands.data(), rands.size(), []()->double { return Math::RandomRange(100, 4e9); }); + + // Verify + const double stddev = Testutil::Stddev(rands); + Assert::IsTrue(stddev >= 1000000, (std::wstringstream() << stddev).str().c_str()); + + return; + } + }; +} diff --git a/Test/Matrix4x4.cpp b/Test/Matrix4x4.cpp new file mode 100644 index 0000000..0a0747d --- /dev/null +++ b/Test/Matrix4x4.cpp @@ -0,0 +1,984 @@ +#include "CppUnitTest.h" +#include "../Eule/Matrix4x4.h" +#include "../Eule/Vector3.h" +#include "../_TestingUtilities/HandyMacros.h" +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace Matrices +{ + TEST_CLASS(_Matrix4x4) + { + private: + std::mt19937 rng; + public: + // Constructor + _Matrix4x4() + { + rng = std::mt19937((std::random_device())()); + return; + } + + // Tests that a freshly created matrix is an identity matrix + TEST_METHOD(New_Matrix_Is_Identity) + { + Matrix4x4 mat; + + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + if (i == j) + Assert::AreEqual(1.0, mat[i][j]); + else + Assert::AreEqual(0.0, mat[i][j]); + + return; + } + + // Test if setting values via array descriptors works + TEST_METHOD(Can_Set_Values_ArrayDescriptor) + { + Matrix4x4 mat; + mat[0][0] = 1; + mat[0][1] = 2; + mat[0][2] = 3; + mat[0][3] = 4; + mat[1][0] = 5; + mat[1][1] = 6; + mat[1][2] = 7; + mat[1][3] = 8; + mat[2][0] = 9; + mat[2][1] = 10; + mat[2][2] = 11; + mat[2][3] = 12; + mat[3][0] = 13; + mat[3][1] = 14; + mat[3][2] = 15; + mat[3][3] = 16; + + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + Assert::AreEqual((double)(i * 4 + j + 1), mat[i][j]); + + return; + } + + // Tests if setting values via letters works + TEST_METHOD(Can_Set_Values_Letters) + { + Matrix4x4 mat; + mat.a = 1; + mat.b = 2; + mat.c = 3; + mat.d = 4; + mat.e = 5; + mat.f = 6; + mat.g = 7; + mat.h = 8; + mat.i = 9; + mat.j = 10; + mat.k = 11; + mat.l = 12; + mat.m = 13; + mat.n = 14; + mat.o = 15; + mat.p = 16; + + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + Assert::AreEqual((double)(i * 4 + j + 1), mat[i][j]); + + return; + } + + // Tests if setting values via multiple initializer lists works + TEST_METHOD(Can_Set_Values_Multiple_Initializer_Lists) + { + Matrix4x4 mat; + mat[0] = { 1, 2, 3, 4 }; + mat[1] = { 5, 6, 7, 8 }; + mat[2] = { 9, 10, 11, 12 }; + mat[3] = { 13, 14, 15, 16 }; + + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + Assert::AreEqual((double)(i * 4 + j + 1), mat[i][j]); + + return; + } + + // Tests if values can be read correctly from the reference variables + TEST_METHOD(Can_Read_Letters) + { + Matrix4x4 mat; + + // Populate matrix + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + mat[i][j] = (double)(i * 4 + j + 1); + + // Check if values can be read + Assert::AreEqual( 1.0, mat.a); + Assert::AreEqual( 2.0, mat.b); + Assert::AreEqual( 3.0, mat.c); + Assert::AreEqual( 4.0, mat.d); + Assert::AreEqual( 5.0, mat.e); + Assert::AreEqual( 6.0, mat.f); + Assert::AreEqual( 7.0, mat.g); + Assert::AreEqual( 8.0, mat.h); + Assert::AreEqual( 9.0, mat.i); + Assert::AreEqual(10.0, mat.j); + Assert::AreEqual(11.0, mat.k); + Assert::AreEqual(12.0, mat.l); + Assert::AreEqual(13.0, mat.m); + Assert::AreEqual(14.0, mat.n); + Assert::AreEqual(15.0, mat.o); + Assert::AreEqual(16.0, mat.p); + + return; + } + + // Tests if the copy constructor results in the same values as the reference given + TEST_METHOD(CopyConstructor_Equal_Values) + { + Matrix4x4 mat1; + + // Fill with values + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + mat1[i][j] = i * 4.0 + j; + + // Copy + Matrix4x4 mat2(mat1); + + // Both equal? + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + Assert::AreEqual(mat1[i][j], mat2[i][j]); + + return; + } + + // Tests if the equals operator results in the same values as the reference given + TEST_METHOD(Copy_Via_Equals_Operator) + { + Matrix4x4 mat1; + + // Fill with values + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + mat1[i][j] = i * 4.0 + j; + + // Copy + Matrix4x4 mat2 = mat1; + + // Both equal? + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + Assert::AreEqual(mat1[i][j], mat2[i][j]); + + return; + } + + // Tests if the values of a matrix constructed via a copy constructor can be changed without modifying the object copied from + TEST_METHOD(Copy_Is_Independent_CopyConstructor) + { + Matrix4x4 mat1; + + // Fill with values + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + mat1[i][j] = i * 4.0 + j; + + // Copy + Matrix4x4 mat2(mat1); + + // Change values in mat2 + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + mat2[i][j] *= -99; + + // Is mat1 untouched? + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + Assert::AreEqual((double)(i * 4 + j), mat1[i][j]); + + // Are the values of mat2 correct? + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + Assert::AreEqual((double)(i * 4 + j) * -99, mat2[i][j]); + + return; + } + + // Tests if the values of a matrix constructed copied via the equals operator can be changed without modifying the object copied from + TEST_METHOD(Copy_Is_Independent_EqualOperator) + { + Matrix4x4 mat1; + + // Fill with values + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + mat1[i][j] = i * 4.0 + j; + + // Copy + Matrix4x4 mat2 = mat1; + + // Change values in mat2 + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + mat2[i][j] *= -99; + + // Is mat1 untouched? + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + Assert::AreEqual((double)(i * 4 + j), mat1[i][j]); + + // Are the values of mat2 correct? + for (std::size_t i = 0; i < 4; i++) + for (std::size_t j = 0; j < 4; j++) + Assert::AreEqual((double)(i * 4 + j) * -99, mat2[i][j]); + + return; + } + + // Tests that copying via operator= works + TEST_METHOD(Copy_Operator) + { + // Setup + Matrix4x4 a; + a[0] = { 1, 0, 0, 5 }; + a[1] = { 2, 0, 0, 6 }; + a[2] = { 3, 0, 0, 7 }; + a[3] = { 4, 0, 0, 8 }; + + Matrix4x4 a_toCopy; + a_toCopy[0] = { 1, 0, 0, 5 }; + a_toCopy[1] = { 2, 0, 0, 6 }; + a_toCopy[2] = { 3, 0, 0, 7 }; + a_toCopy[3] = { 4, 0, 0, 8 }; + + // Exercise + Matrix4x4 b = a_toCopy; + + // Verify + Assert::IsTrue(a == a_toCopy, L"a got destroyed!"); + Assert::IsTrue(b == a, L"a does not match b!"); + + return; + } + + // Tests that moving via operator= works + TEST_METHOD(Move_Operator) + { + // Setup + Matrix4x4 a; + a[0] = { 1, 0, 0, 5 }; + a[1] = { 2, 0, 0, 6 }; + a[2] = { 3, 0, 0, 7 }; + a[3] = { 4, 0, 0, 8 }; + + Matrix4x4 a_backup = a; + + // Exercise + Matrix4x4 b = std::move(a); + + // Verify + Assert::IsTrue(b == a_backup, L"Values don't match!"); + + return; + } + + // Tests if the multiply-equals (*=) operator works as intended + TEST_METHOD(Multiplication_Equals) + { + // Populate 1 + Matrix4x4 mat1; + mat1[0] = { 12, 33, 43, 34 }; + mat1[1] = { 0, 4, 3, 11 }; + mat1[2] = { 76, 5, 42, 4 }; + mat1[3] = { 0, 0, 0, 1 }; + + // Populate 2 + Matrix4x4 mat2; + mat2[0] = { 32, 11, 23, 6 }; + mat2[1] = { 54, 23, 64, 9 }; + mat2[2] = { 64, 43, 12, 16 }; + mat2[3] = { 0, 0, 0, 1 }; + + + // Multiply + mat1 *= mat2; + + // Check + Matrix4x4 expected; + expected[0] = { 4918.0, 2740.0, 2904.0, 40 }; + expected[1] = { 408.0, 221.0, 292.0, 20 }; + expected[2] = { 5390.0, 2757.0, 2572.0, 20 }; + expected[3] = { 0, 0, 0, 1 }; + + Assert::IsTrue(mat1.v == expected.v); + + return; + } + + // Tests if the multiplication operator works as intended + TEST_METHOD(Multiplication) + { + // Populate 1 + Matrix4x4 mat1; + mat1[0] = { 12, 33, 43, 34 }; + mat1[1] = { 0, 4, 3, 11 }; + mat1[2] = { 76, 5, 42, 4 }; + mat1[3] = { 0, 0, 0, 1 }; + + // Populate 2 + Matrix4x4 mat2; + mat2[0] = { 32, 11, 23, 6 }; + mat2[1] = { 54, 23, 64, 9 }; + mat2[2] = { 64, 43, 12, 16 }; + mat2[3] = { 0, 0, 0, 1 }; + + // Multiply + Matrix4x4 mat3 = mat1 * mat2; + + // Check + Matrix4x4 expected; + expected[0] = { 4918.0, 2740.0, 2904.0, 40 }; + expected[1] = { 408.0, 221.0, 292.0, 20 }; + expected[2] = { 5390.0, 2757.0, 2572.0, 20 }; + expected[3] = { 0, 0, 0, 1 }; + + Assert::IsTrue(mat3.v == expected.v); + + return; + } + + // Tests if GetTranslationComponent returns the correct values + TEST_METHOD(GetTranslationComponent) + { + // Create and populate mat + Matrix4x4 mat; + mat.d = 69; + mat.h = 32; + mat.l = 16; + + // Get translation component + Vector3d translation = mat.GetTranslationComponent(); + + // Check + Assert::AreEqual(69.0, translation.x); + Assert::AreEqual(32.0, translation.y); + Assert::AreEqual(16.0, translation.z); + + return; + } + + // Tests if SetTranslationComponent returns the correct values + TEST_METHOD(SetTranslationComponent) + { + // Create and populate mat + Vector3d translation(69, 32, 16); + + // Set translation component + Matrix4x4 mat; + mat.SetTranslationComponent(translation); + + // Check + Assert::AreEqual(69.0, mat.d); + Assert::AreEqual(32.0, mat.h); + Assert::AreEqual(16.0, mat.l); + + return; + } + + // Tests that transpose3x3 works + TEST_METHOD(Transpose3x3) + { + Matrix4x4 m; + m[0] = { 0, 0, 0, 0 }; + m[1] = { 3, 0, 4, 0 }; + m[2] = { 0, 0, 2, 5 }; + m[3] = { 9, 0, 6, 0 }; + + Matrix4x4 target; + target[0] = { 0, 3, 0, 0 }; + target[1] = { 0, 0, 0, 0 }; + target[2] = { 0, 4, 2, 5 }; + target[3] = { 9, 0, 6, 0 }; + + // Create debug output + std::wstringstream wss; + wss << std::endl + << "Actual: " << m.Transpose3x3() << std::endl + << "Target: " << target << std::endl; + + Assert::IsTrue(target == m.Transpose3x3(), wss.str().c_str()); + + return; + } + + // Tests that transpose4x4 works + TEST_METHOD(Transpose4x4) + { + Matrix4x4 m; + m[0] = { 0, 0, 0, 0 }; + m[1] = { 3, 0, 4, 0 }; + m[2] = { 0, 0, 2, 5 }; + m[3] = { 9, 0, 6, 0 }; + + Matrix4x4 target; + target[0] = { 0, 3, 0, 9 }; + target[1] = { 0, 0, 0, 0 }; + target[2] = { 0, 4, 2, 6 }; + target[3] = { 0, 0, 5, 0 }; + + // Create debug output + std::wstringstream wss; + wss << std::endl + << "Actual: " << m.Transpose4x4() << std::endl + << "Target: " << target << std::endl; + + Assert::IsTrue(target == m.Transpose4x4(), wss.str().c_str()); + + return; + } + + // Tests that IsInvertible3x3 works -> true + TEST_METHOD(Is_Invertible_3x3_True) + { + Matrix4x4 m; + m[0] = { 0.56601, -0.87207, 0.52783, 488.00000 }; + m[1] = { -0.55281, 0.41590, 0.85470, 500.00000 }; + m[2] = { -1.09497, -0.66076, -0.15866, -155.09390 }; + m[3] = { 0.00000, 0.00000, 0.00000, 0.00000 }; + + Assert::IsTrue(m.IsInversible3x3()); + + return; + } + + // Tests that IsInvertible3x3 works -> false + TEST_METHOD(Is_Invertible_3x3_False) + { + Matrix4x4 m; + m[0] = { 0, 0, 1, 0 }; + m[1] = { 0, 0, 0, 0 }; + m[2] = { 0, 0, 0, 0 }; + m[3] = { 0, 0, 0, 0 }; + + Assert::IsFalse(m.IsInversible3x3()); + + return; + } + + // Tests that IsInvertible4x4 works -> true + TEST_METHOD(Is_Invertible_4x4_True) + { + Matrix4x4 m; + m[0] = { 0.56601, -0.87207, 0.52783, 488.00000 }; + m[1] = { -0.55281, 0.41590, 0.85470, 500.00000 }; + m[2] = { -1.09497, -0.66076, -0.15866, -155.09390 }; + m[3] = { 0.00000, 0.00000, 0.00000, 1.00000 }; + + Assert::IsTrue(m.IsInversible4x4()); + + return; + } + + // Tests that IsInvertible4x4 works -> false + TEST_METHOD(Is_Invertible_4x4_False) + { + Matrix4x4 m; + m[0] = { 0.56601, -0.87207, 0.52783, 488.00000 }; + m[1] = { -0.55281, 0.41590, 0.85470, 500.00000 }; + m[2] = { -1.09497, -0.66076, -0.15866, -155.09390 }; + m[3] = { 0.00000, 0.00000, 0.00000, 0.00000 }; + + Assert::IsFalse(m.IsInversible4x4()); + + return; + } + + // Tests that inverting a 3x3 matrix (scale, rotation, translation) works + TEST_METHOD(Inverse3x3) + { + // Invert 50 randomly generated matrices + for (std::size_t i = 0; i < 50;) + { + Matrix4x4 m; + m[0] = { LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE }; + m[1] = { LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE }; + m[2] = { LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE }; + m[3] = { LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE }; + + if (m.IsInversible3x3()) + { + Matrix4x4 inv_m = m.Inverse3x3(); + Matrix4x4 result = m * inv_m; + + // Create debug output + std::wstringstream wss; + wss << std::endl + << "i: " << i << std::endl + << "Actual: " << result << std::endl + << "Target: " << Matrix4x4() << std::endl; + + Assert::IsTrue(result.Similar(Matrix4x4()), wss.str().c_str()); // Default constructor is identity matrix + i++; + } + } + + return; + } + + // Tests that inverting a 4x4 matrix works + TEST_METHOD(Inverse4x4) + { + // Invert 50 randomly generated matrices + for (std::size_t i = 0; i < 50;) + { + Matrix4x4 m; + m[0] = { LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE }; + m[1] = { LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE }; + m[2] = { LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE }; + m[3] = { LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE }; + + if (m.IsInversible4x4()) + { + Matrix4x4 inv_m = m.Inverse4x4(); + + // Create debug output + std::wstringstream wss; + wss << std::endl + << "i: " << i << std::endl + << "Actual: " << m.Multiply4x4(inv_m) << std::endl + << "Target: " << Matrix4x4() << std::endl; + + Assert::IsTrue((m.Multiply4x4(inv_m)).Similar(Matrix4x4(), 0.0001), wss.str().c_str()); // Default constructor is identity matrix + i++; + } + } + + return; + } + + // Tests the Multiply4x4 method, which does an actual 4x4 multiplication + TEST_METHOD(Multiply4x4) + { + Matrix4x4 a; + a[0] = { 0, 1, 2, 3 }; + a[1] = { 4, 5, 6, 7 }; + a[2] = { 8, 9, 0, 1 }; + a[3] = { 2, 3, 4, 5 }; + + Matrix4x4 b; + b[0] = { 9, 8, 7, 6 }; + b[1] = { 5, 4, 3, 2 }; + b[2] = { 1, 0, 9, 8 }; + b[3] = { 7, 6, 5, 4 }; + + Matrix4x4 e; // Expected + e[0] = { 28, 22, 36, 30 }; + e[1] = { 116, 94, 132, 110 }; + e[2] = { 124, 106, 88, 70 }; + e[3] = { 72, 58, 84, 70 }; + + // Create debug output + std::wstringstream wss; + wss << std::endl + << "Actual: " << a.Multiply4x4(b) << std::endl + << "Target: " << e << std::endl; + + Assert::IsTrue(a.Multiply4x4(b).Similar(e), wss.str().c_str()); + } + + // Tests the DropTranslationComponents method. It should return itself, with d,h,l = 0,0,0 + TEST_METHOD(DropTranslationComponents) + { + // Setup + Matrix4x4 a; + a[0] = { 0, 1, 2, 3 }; + a[1] = { 4, 5, 6, 7 }; + a[2] = { 8, 9, 0, 1 }; + a[3] = { 2, 3, 4, 5 }; + + Matrix4x4 e; // Expected + e[0] = { 0, 1, 2, 0 }; + e[1] = { 4, 5, 6, 0 }; + e[2] = { 8, 9, 0, 0 }; + e[3] = { 2, 3, 4, 5 }; + + // Exercise, Verify + Assert::IsTrue(e == a.DropTranslationComponents()); + return; + } + + //! Tests that adding two matrices works as intended + TEST_METHOD(Operator_Add) + { + // Setup + Matrix4x4 a; + a[0] = { -9, 5, 6, 7 }; + a[1] = { 1, 2, 5, 0 }; + a[2] = { 2, -2, 7, 5 }; + a[3] = { 3, 0, 3, 0 }; + + Matrix4x4 b; + b[0] = { 6, 0, 5, 0 }; + b[1] = { 3, 0, 1, 1 }; + b[2] = { 1, 7, 2, 7 }; + b[3] = { 0, 2, 0, 0 }; + + Matrix4x4 exp; // Expected + exp[0] = { -3, 5, 11, 7 }; + exp[1] = { 4, 2, 6, 1 }; + exp[2] = { 3, 5, 9, 12 }; + exp[3] = { 3, 2, 3, 0 }; + + // Exercise + Matrix4x4 result = a + b; + + // Verify + Assert::IsTrue(exp == result); + + return; + } + + //! Tests that adding two matrices works as intended + TEST_METHOD(Operator_AddEquals) + { + // Setup + Matrix4x4 a; + a[0] = { -9, 5, 6, 7 }; + a[1] = { 1, 2, 5, 0 }; + a[2] = { 2, -2, 7, 5 }; + a[3] = { 3, 0, 3, 0 }; + + Matrix4x4 b; + b[0] = { 6, 0, 5, 0 }; + b[1] = { 3, 0, 1, 1 }; + b[2] = { 1, 7, 2, 7 }; + b[3] = { 0, 2, 0, 0 }; + + Matrix4x4 exp; // Expected + exp[0] = { -3, 5, 11, 7 }; + exp[1] = { 4, 2, 6, 1 }; + exp[2] = { 3, 5, 9, 12 }; + exp[3] = { 3, 2, 3, 0 }; + + // Exercise + a += b; + + // Verify + Assert::IsTrue(exp == a); + + return; + } + + //! Tests that subtracting two matrices works as intended + TEST_METHOD(Operator_Sub) + { + // Setup + Matrix4x4 a; + a[0] = { -9, 5, 6, 7 }; + a[1] = { 1, 2, 5, 0 }; + a[2] = { 2, -2, 7, 5 }; + a[3] = { 3, 0, 3, 0 }; + + Matrix4x4 b; + b[0] = { -6, -0, -5, -0 }; + b[1] = { -3, -0, -1, -1 }; + b[2] = { -1, -7, -2, -7 }; + b[3] = { -0, -2, -0, -0 }; + + Matrix4x4 exp; // Expected + exp[0] = { -3, 5, 11, 7 }; + exp[1] = { 4, 2, 6, 1 }; + exp[2] = { 3, 5, 9, 12 }; + exp[3] = { 3, 2, 3, 0 }; + + // Exercise + Matrix4x4 result = a - b; + + // Verify + Assert::IsTrue(exp == result); + + return; + } + + //! Tests that subtracting two matrices works as intended + TEST_METHOD(Operator_SubEuqals) + { + // Setup + Matrix4x4 a; + a[0] = { -9, 5, 6, 7 }; + a[1] = { 1, 2, 5, 0 }; + a[2] = { 2, -2, 7, 5 }; + a[3] = { 3, 0, 3, 0 }; + + Matrix4x4 b; + b[0] = { -6, -0, -5, -0 }; + b[1] = { -3, -0, -1, -1 }; + b[2] = { -1, -7, -2, -7 }; + b[3] = { -0, -2, -0, -0 }; + + Matrix4x4 exp; // Expected + exp[0] = { -3, 5, 11, 7 }; + exp[1] = { 4, 2, 6, 1 }; + exp[2] = { 3, 5, 9, 12 }; + exp[3] = { 3, 2, 3, 0 }; + + // Exercise + a -= b; + + // Verify + Assert::IsTrue(exp == a); + + return; + } + + // Tests that the multiplication operator for a double parameter works + TEST_METHOD(Operator_MultiplyDouble) + { + // Setup + Matrix4x4 a; + a[0] = { -9, 5, 6, 7 }; + a[1] = { 1, 2, 5, 0 }; + a[2] = { 2,-2, 7, 5 }; + a[3] = { 3, 0, 3, 0 }; + + double s = LARGE_RAND_DOUBLE; + + Matrix4x4 exp; // Expected + exp[0] = { -9*s, 5*s, 6*s, 7*s }; + exp[1] = { 1*s, 2*s, 5*s, 0*s }; + exp[2] = { 2*s,-2*s, 7*s, 5*s }; + exp[3] = { 3*s, 0*s, 3*s, 0*s }; + + // Exercise + Matrix4x4 result = a * s; + + // Verify + Assert::IsTrue(exp.Similar(result)); + + return; + } + + // Tests that the multiplication operator for a double parameter works + TEST_METHOD(Operator_MultiplyEqualsDouble) + { + // Setup + Matrix4x4 a; + a[0] = { -9, 5, 6, 7 }; + a[1] = { 1, 2, 5, 0 }; + a[2] = { 2,-2, 7, 5 }; + a[3] = { 3, 0, 3, 0 }; + + double s = LARGE_RAND_DOUBLE; + + Matrix4x4 exp; // Expected + exp[0] = { -9*s, 5*s, 6*s, 7*s }; + exp[1] = { 1*s, 2*s, 5*s, 0*s }; + exp[2] = { 2*s,-2*s, 7*s, 5*s }; + exp[3] = { 3*s, 0*s, 3*s, 0*s }; + + // Exercise + a *= s; + + // Verify + Assert::IsTrue(exp.Similar(a)); + + return; + } + + // Tests that the division operator for a double parameter works + TEST_METHOD(Operator_DivideDouble) + { + // Setup + Matrix4x4 a; + a[0] = { -9, 5, 6, 7 }; + a[1] = { 1, 2, 5, 0 }; + a[2] = { 2,-2, 7, 5 }; + a[3] = { 3, 0, 3, 0 }; + + double s = LARGE_RAND_DOUBLE; + + Matrix4x4 exp; // Expected + exp[0] = { -9/s, 5/s, 6/s, 7/s }; + exp[1] = { 1/s, 2/s, 5/s, 0/s }; + exp[2] = { 2/s,-2/s, 7/s, 5/s }; + exp[3] = { 3/s, 0/s, 3/s, 0/s }; + + // Exercise + Matrix4x4 result = a / s; + + // Verify + Assert::IsTrue(exp.Similar(result)); + + return; + } + + // Tests that the division operator for a double parameter works + TEST_METHOD(Operator_DivideEqualsDouble) + { + // Setup + Matrix4x4 a; + a[0] = { -9, 5, 6, 7 }; + a[1] = { 1, 2, 5, 0 }; + a[2] = { 2,-2, 7, 5 }; + a[3] = { 3, 0, 3, 0 }; + + double s = LARGE_RAND_DOUBLE; + + Matrix4x4 exp; // Expected + exp[0] = { -9/s, 5/s, 6/s, 7/s }; + exp[1] = { 1/s, 2/s, 5/s, 0/s }; + exp[2] = { 2/s,-2/s, 7/s, 5/s }; + exp[3] = { 3/s, 0/s, 3/s, 0/s }; + + // Exercise + a /= s; + + // Verify + Assert::IsTrue(exp.Similar(a)); + + return; + } + + // Tests that matrix division (multiplication with inverse) works + TEST_METHOD(Operator_DivideMatrix) + { + // Setup + Matrix4x4 a; + a[0] = { 0.0503814, 0.3314391, 0.9421304, 33 }; + a[1] = { 0.4941404, 0.8115034, -0.3119095, 44 }; + a[2] = { -0.8679211, 0.4812591, -0.1228928 , 55 }; + a[3] = { 0, 0, 0, 1 }; + + Matrix4x4 b; + b[0] = { -0.3980391, -0.5301175, -0.7486925, 3 }; + b[1] = { 0.3352839, 0.6756021, -0.6566175, 4 }; + b[2] = { 0.8539026, -0.5123839, -0.0911762 , 5 }; + b[3] = { 0, 0, 0, 1 }; + + Matrix4x4 expected = a * b.Inverse3x3(); + // Just to be sure, but should already be set + expected.SetTranslationComponent(Vector3d(30, 40, 50)); + + // Exercise + Matrix4x4 actual = a / b; + + // Verify + Assert::IsTrue(expected.Similar(actual)); + + return; + } + + // Tests that matrix division (multiplication with inverse) works + TEST_METHOD(Operator_DivideEqualsMatrix) + { + // Setup + Matrix4x4 a; + a[0] = { 0.0503814, 0.3314391, 0.9421304, 33 }; + a[1] = { 0.4941404, 0.8115034, -0.3119095, 44 }; + a[2] = { -0.8679211, 0.4812591, -0.1228928 , 55 }; + a[3] = { 0, 0, 0, 1 }; + + Matrix4x4 b; + b[0] = { -0.3980391, -0.5301175, -0.7486925, 3 }; + b[1] = { 0.3352839, 0.6756021, -0.6566175, 4 }; + b[2] = { 0.8539026, -0.5123839, -0.0911762 , 5 }; + b[3] = { 0, 0, 0, 1 }; + + Matrix4x4 expected = a * b.Inverse3x3(); + // Just to be sure, but should already be set + expected.SetTranslationComponent(Vector3d(30, 40, 50)); + + // Exercise + a /= b; + + // Verify + Assert::IsTrue(expected.Similar(a)); + + return; + } + + // Tests that Math::Similar() works -> true + TEST_METHOD(Similar_True) + { + Matrix4x4 a; + a[0] = { 1, 0, 0, 0 }; + a[1] = { 0, 1, 0, 0 }; + a[2] = { 0, 0, 1, 0 }; + a[3] = { 0, 0, 0, 1 }; + + Matrix4x4 b; + b[0] = { 1, -9e-20, 2e-8, 9e-19 }; + b[1] = { 12e-19, 1, -20e-15, -6.9e-29 }; + b[2] = { -69e-25, 13e-23, 1, 4.301e-15 }; + b[3] = { -23e-19, 23e-19, 25e-7, 1 }; + + Assert::IsTrue(a.Similar(b)); + } + + // Tests that Math::Similar() works -> false + TEST_METHOD(Similar_False) + { + Matrix4x4 a; + a[0] = { 1, 0, 0, 0 }; + a[1] = { 0, 1, 0, 0 }; + a[2] = { 0, 0, 1, 0 }; + a[3] = { 0, 0, 0, 1 }; + + Matrix4x4 b; + b[0] = { 1, -9e-20, 2e-8, 9e-19 }; + b[1] = { 12e-19, 1, -20e-15, 0.05 }; // <-- + b[2] = { -69e-25, 13e-23, 1, 4.301e-15 }; + b[3] = { -23e-19, 23e-19, 25e-7, 1 }; + + Assert::IsFalse(a.Similar(b)); + } + + // Tests if the equal operator (==) and not-equals operator (!=) work (equal: false) + TEST_METHOD(Operator_Equals_NotEquals_False) + { + Matrix4x4 a; + a[0] = { 0x0, 0x1, 0x2, 0x3 }; + a[1] = { 0x4, 0x5, 0x6, 0x7 }; + a[2] = { 0x8, 0x9, 0xA, 0xB }; + a[3] = { 0xC, 0xD, 0xE, 0xF }; + + Matrix4x4 b; + b[3] = { 0xF ,0xD, 0xE, 0xC }; + b[2] = { 0xB ,0x9, 0xA, 0x8 }; + b[1] = { 0x7 ,0x5, 0x6, 0x4 }; + b[0] = { 0x3 ,0x1, 0x2, 0x0 }; + + Assert::IsFalse(a == b); + Assert::IsTrue(a != b); + return; + } + + // Tests if the equal operator (==) and not-equals operator (!=) work (equal: true) + TEST_METHOD(Operator_Equals_False) + { + Matrix4x4 a; + a[0] = { 0x0, 0x1, 0x2, 0x3 }; + a[1] = { 0x4, 0x5, 0x6, 0x7 }; + a[2] = { 0x8, 0x9, 0xA, 0xB }; + a[3] = { 0xC, 0xD, 0xE, 0xF }; + + Matrix4x4 b; + b[0] = { 0x0, 0x1, 0x2, 0x3 }; + b[1] = { 0x4, 0x5, 0x6, 0x7 }; + b[2] = { 0x8, 0x9, 0xA, 0xB }; + b[3] = { 0xC, 0xD, 0xE, 0xF }; + + Assert::IsTrue(a == b); + Assert::IsFalse(a != b); + return; + } + }; +} diff --git a/Test/Quaternion.cpp b/Test/Quaternion.cpp new file mode 100644 index 0000000..0444330 --- /dev/null +++ b/Test/Quaternion.cpp @@ -0,0 +1,305 @@ +#include "CppUnitTest.h" +#include "../Eule/Quaternion.h" +#include "../Eule/Math.h" +#include "../_TestingUtilities/HandyMacros.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace TransformRelated +{ + TEST_CLASS(_Quaternion) + { + private: + std::mt19937 rng; + + public: + // Constructor + _Quaternion() + { + rng = std::mt19937((std::random_device())()); + return; + } + + // Tests that if constructed with the default constructor, that all values are 0 (but w should be 1) + TEST_METHOD(Default_Constructor_All_0) + { + Quaternion q; + Assert::IsTrue(Vector4d(0, 0, 0, 1) == q.GetRawValues()); + + return; + } + + // Tests that getting and setting raw values works + TEST_METHOD(Can_Set_Get_Raw_Values) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + Vector4d v( + rng() % 90, + rng() % 90, + rng() % 90, + rng() % 90 + ); + + Quaternion q(Vector4d(0, 0, 0, 0)); // Garbage values + + q.SetRawValues(v); + Assert::IsTrue(v.Similar(q.GetRawValues())); + } + + return; + } + + // Tests that retreiving euler angles (without gimbal lock) results in the same values as put in + TEST_METHOD(To_Euler_From_Euler) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Create vector + Vector3d eul( + rng() % 90, + rng() % 90, + rng() % 90 + ); + + // Create quaternion from vector + Quaternion q(eul); + + // Create debug output + std::wstringstream wss; + wss << std::endl + << "Actual vals: " << q.ToEulerAngles() << std::endl + << "Target vals: " << eul << std::endl; + + // Assertion + Assert::IsTrue(eul.Similar(q.ToEulerAngles()), wss.str().c_str()); + } + + return; + } + + // Tests that adding angles (0,0,0) does not modify the quaternion + TEST_METHOD(Add_Angles_0_Does_Nothing) + { + Quaternion a(Vector3d(0, -45, 45)); + Quaternion b(Vector3d(0, 0, 0)); + + Assert::IsTrue(Vector3d(0, -45, 45).Similar((a * b).ToEulerAngles())); + + return; + } + + // Tests that subtracting angles (0,0,0) does not modify the quaternion + TEST_METHOD(Sub_Angles_0_Does_Nothing) + { + Quaternion a(Vector3d(0, -45, 45)); + Quaternion b(Vector3d(0, 0, 0)); + + Assert::IsTrue(Vector3d(0, -45, 45).Similar((a / b).ToEulerAngles())); + + return; + } + + // Tests that subtracting by itself always returns (0,0,0) + TEST_METHOD(Sub_Itself_Is_0) + { + // Run test 100 times + for (std::size_t i = 0; i < 100; i++) + { + Quaternion a(Vector3d(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE)); + Assert::IsTrue(Vector3d(0,0,0).Similar((a / a).ToEulerAngles())); + } + + return; + } + + // Tests that rotating a vector is equal to multiplying it with the inverted rotation matrix + TEST_METHOD(RotateVector_Equal_to_RotationMatrix) + { + // Run test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + Quaternion a(Vector3d(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE)); + + Vector3d point(32, 19, -14); + + Assert::IsTrue((point * a.ToRotationMatrix()).Similar(a * point)); + } + + return; + } + + // Tests that a *= b will result in the exact same outcome as a = a * b + TEST_METHOD(MultiplyEquals_Operator_Same_Result_As_Multiply_Operator) + { + // Run tests 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + Quaternion a(Vector3d(rng() % 360, rng() % 360, rng() % 360)); + Quaternion b(Vector3d(rng() % 360, rng() % 360, rng() % 360)); + + // Exercise + Quaternion ref = a * b; + a *= b; + + // Verify + Assert::IsTrue(a.GetRawValues().Similar(ref.GetRawValues())); + } + + return; + } + + // Tests that a /= b will result in the exact same outcome as a = a / b + TEST_METHOD(DivideEquals_Operator_Same_Result_As_Divide_Operator) + { + // Run tests 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + Quaternion a(Vector3d(rng() % 360, rng() % 360, rng() % 360)); + Quaternion b(Vector3d(rng() % 360, rng() % 360, rng() % 360)); + + // Exercise + Quaternion ref = a / b; + a /= b; + + // Verify + Assert::IsTrue(a.GetRawValues().Similar(ref.GetRawValues())); + } + + return; + } + + // Tests basic equals comparison -> true + TEST_METHOD(Basic_EqualsComparison_True) + { + // Run tests 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + Vector3d e(rng() % 360, rng() % 360, rng() % 360); + Quaternion a(e); + Quaternion b(e); + + // Exercise and verify + Assert::IsTrue(a == b); + } + + return; + } + + // Tests basic equals comparison -> true + TEST_METHOD(Basic_EqualsComparison_False) + { + // Run tests 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + Vector3d ae(rng() % 360, rng() % 360, rng() % 360); + Vector3d be(rng() % 360, rng() % 360, rng() % 360); + + // Abort if both vectors are equal + if (ae == be) + { + i--; + continue; + } + + Quaternion a(ae); + Quaternion b(be); + + // Exercise and verify + Assert::IsFalse(a == b); + } + + return; + } + + // Tests that different euler angles return true, if the angle is the same. + // Like [30, -10, 59] == [390, 350, 419] + TEST_METHOD(Equals_Comparison_Same_Rotation_Different_EulerAngles) + { + // Run tests 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + // Create random rotation + Vector3d ae(rng() % 360, rng() % 360, rng() % 360); + + // add or subtract a random multiple of 360 + #define keep_rot_change_values (360.0 * (double)(rng() % 20) * ((rng()%2) ? 1.0 : -1.0)) + Vector3d be(ae.x + keep_rot_change_values, ae.y + keep_rot_change_values, ae.z + keep_rot_change_values); + #undef keep_rot_change_values + + // Create quaternions + Quaternion a(ae); + Quaternion b(be); + + // Exercise & Verify + // Create debug output + + std::wstringstream wss; + wss << "ae: " << ae << std::endl + << "be: " << be << std::endl + << "a: " << a << std::endl + << "b: " << b << std::endl; + + // Assertion + Assert::IsTrue(a == b, wss.str().c_str()); + } + + return; + } + + // Tests basic not-equals comparison -> false + TEST_METHOD(Basic_NotEqualsComparison_False) + { + // Run tests 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + Vector3d e(rng() % 360, rng() % 360, rng() % 360); + Quaternion a(e); + Quaternion b(e); + + // Exercise and verify + Assert::IsFalse(a != b); + } + + return; + } + + // Tests basic not-equals comparison -> true + TEST_METHOD(Basic_NotEqualsComparison_True) + { + // Run tests 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Setup + Vector3d ae(rng() % 360, rng() % 360, rng() % 360); + Vector3d be(rng() % 360, rng() % 360, rng() % 360); + + // Abort if both vectors are equal + if (ae == be) + { + i--; + continue; + } + + Quaternion a(ae); + Quaternion b(be); + + // Exercise and verify + Assert::IsTrue(a != b); + } + + return; + } + }; +} diff --git a/Test/TrapazoidalPrismCollider.cpp b/Test/TrapazoidalPrismCollider.cpp new file mode 100644 index 0000000..ee8ba16 --- /dev/null +++ b/Test/TrapazoidalPrismCollider.cpp @@ -0,0 +1,166 @@ +#include "CppUnitTest.h" +#include "../Eule/TrapazoidalPrismCollider.h" +#include "../Eule/Quaternion.h" +#include "../_TestingUtilities/HandyMacros.h" +#include +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; +using TPC = TrapazoidalPrismCollider; + +namespace Colliders +{ + TEST_CLASS(_TrapazoidalPrismCollider) + { + private: + std::mt19937 rng; + + public: + // Constructor + _TrapazoidalPrismCollider() + { + rng = std::mt19937((std::random_device())()); + return; + + } + + // Tests that all vertices can be set individually, and at once + TEST_METHOD(Can_Set_Each_Vertex) + { + // All vertex values are unique + TPC tpc; + tpc.SetVertex(TPC::FRONT | TPC::LEFT | TPC::BOTTOM, Vector3d(-1, -1, 1) * 1); + tpc.SetVertex(TPC::FRONT | TPC::LEFT | TPC::TOP, Vector3d(-1, 1, 1) * 2); + tpc.SetVertex(TPC::BACK | TPC::LEFT | TPC::BOTTOM, Vector3d(-1, -1, -1) * 3); + tpc.SetVertex(TPC::BACK | TPC::LEFT | TPC::TOP, Vector3d(-1, 1, -1) * 4); + tpc.SetVertex(TPC::FRONT | TPC::RIGHT | TPC::BOTTOM, Vector3d(1, -1, 1) * 5); + tpc.SetVertex(TPC::FRONT | TPC::RIGHT | TPC::TOP, Vector3d(1, 1, 1) * 6); + tpc.SetVertex(TPC::BACK | TPC::RIGHT | TPC::BOTTOM, Vector3d(1, -1, -1) * 7); + tpc.SetVertex(TPC::BACK | TPC::RIGHT | TPC::TOP, Vector3d(1, 1, -1) * 8); + + Assert::IsTrue(tpc.GetVertex(TPC::FRONT | TPC::LEFT | TPC::BOTTOM) == (Vector3d(-1, -1, 1) * 1), L"FRONT|LEFT|BOTTOM"); + Assert::IsTrue(tpc.GetVertex(TPC::FRONT | TPC::LEFT | TPC::TOP) == (Vector3d(-1, 1, 1) * 2), L"FRONT|LEFT|TOP"); + Assert::IsTrue(tpc.GetVertex(TPC::BACK | TPC::LEFT | TPC::BOTTOM) == (Vector3d(-1, -1, -1) * 3), L"BACK|LEFT|BOTTOM"); + Assert::IsTrue(tpc.GetVertex(TPC::BACK | TPC::LEFT | TPC::TOP) == (Vector3d(-1, 1, -1) * 4), L"BACK|LEFT|TOP"); + Assert::IsTrue(tpc.GetVertex(TPC::FRONT | TPC::RIGHT | TPC::BOTTOM) == (Vector3d(1, -1, 1) * 5), L"FRONT|RIGHT|BOTTOM"); + Assert::IsTrue(tpc.GetVertex(TPC::FRONT | TPC::RIGHT | TPC::TOP) == (Vector3d(1, 1, 1) * 6), L"FRONT|RIGHT|TOP"); + Assert::IsTrue(tpc.GetVertex(TPC::BACK | TPC::RIGHT | TPC::BOTTOM) == (Vector3d(1, -1, -1) * 7), L"BACK|RIGHT|BOTTOM"); + Assert::IsTrue(tpc.GetVertex(TPC::BACK | TPC::RIGHT | TPC::TOP) == (Vector3d(1, 1, -1) * 8), L"BACK|RIGHT|TOP"); + + return; + } + + // Tests that points inside work. + // For this, we define a few points around [0,0,0] and check if they are contained. + // We then rotate the collider, and check again + // Gets repeated for every possible rotation with a min-distance per axis of 2 deg + TEST_METHOD(Points_Inside) + { + // Setup + // Define known-inside points + std::array knownInsides = { + Vector3d( 1,-1, 1), + Vector3d(-1,-1, 1), + Vector3d( 1, 1, 1), + Vector3d(-1, 1, 1), + Vector3d( 1,-1,-1), + Vector3d(-1,-1,-1), + Vector3d( 1, 1,-1), + Vector3d(-1, 1,-1), + Vector3d( 0, 0, 0), + }; + + // Create collider, a cube of size 10^3 around the center + TPC tpc; + + // Exercise + // Now check that these points are inside for all these possible angles + #ifndef _DEBUG + constexpr double stepSize = 2; + #else + constexpr double stepSize = 32; + #endif + for (double theta = 0; theta < 360.01; theta += stepSize) + for (double phi = 0; phi < 360.01; phi += 2) + for (double alpha = 0; alpha < 360.01; alpha += stepSize) + { + // Rotate box + tpc.SetVertex(TPC::FRONT | TPC::LEFT | TPC::BOTTOM, Quaternion({theta, phi, alpha}) * (Vector3d(-1, -1, 1) * 10)); + tpc.SetVertex(TPC::FRONT | TPC::LEFT | TPC::TOP, Quaternion({theta, phi, alpha}) * (Vector3d(-1, 1, 1) * 10)); + tpc.SetVertex(TPC::BACK | TPC::LEFT | TPC::BOTTOM, Quaternion({theta, phi, alpha}) * (Vector3d(-1, -1, -1) * 10)); + tpc.SetVertex(TPC::BACK | TPC::LEFT | TPC::TOP, Quaternion({theta, phi, alpha}) * (Vector3d(-1, 1, -1) * 10)); + tpc.SetVertex(TPC::FRONT | TPC::RIGHT | TPC::BOTTOM, Quaternion({theta, phi, alpha}) * (Vector3d(1, -1, 1) * 10)); + tpc.SetVertex(TPC::FRONT | TPC::RIGHT | TPC::TOP, Quaternion({theta, phi, alpha}) * (Vector3d(1, 1, 1) * 10)); + tpc.SetVertex(TPC::BACK | TPC::RIGHT | TPC::BOTTOM, Quaternion({theta, phi, alpha}) * (Vector3d(1, -1, -1) * 10)); + tpc.SetVertex(TPC::BACK | TPC::RIGHT | TPC::TOP, Quaternion({theta, phi, alpha}) * (Vector3d(1, 1, -1) * 10)); + + // Verify + // Verify that all are inside + for (const Vector3d& v : knownInsides) + Assert::IsTrue(tpc.Contains(v)); + } + + return; + } + + // Tests that points outside work. + // For this, we define a few points that are definitely outside for various reasons and check if they are not contained. + // We then rotate the collider, and check again + // Gets repeated for every possible rotation with a min-distance per axis of 2 deg + TEST_METHOD(Points_Outside) + { + // Setup + // Define known-inside points + std::array knownOutsides = { + Vector3d(-199, 0, 0), + Vector3d(0, -199, 0), + Vector3d(0, 0, -199), + Vector3d(199, 0, 0), + Vector3d(0, 199, 0), + Vector3d(0, 0, 199), + Vector3d( 20, -20, 0), + Vector3d(50, 50, 50), + Vector3d(50, -50, 0), + Vector3d( 0, 0, 29), + Vector3d( 2, 1, -18), + Vector3d( -1, 29, -1), + Vector3d( 0, -50, -50), + Vector3d( -50, -50, -50) + }; + + // Create collider, a cube of size 10^3 around the center + TPC tpc; + + // Exercise + // Now check that these points are inside for all these possible angles + #ifndef _DEBUG + constexpr double stepSize = 2; + #else + constexpr double stepSize = 32; + #endif + for (double theta = 0; theta < 360.01; theta += stepSize) + for (double phi = 0; phi < 360.01; phi += 2) + for (double alpha = 0; alpha < 360.01; alpha += stepSize) + { + // Rotate box + tpc.SetVertex(TPC::FRONT | TPC::LEFT | TPC::BOTTOM, Quaternion({ theta, phi, alpha }) * (Vector3d(-1, -1, 1) * 10)); + tpc.SetVertex(TPC::FRONT | TPC::LEFT | TPC::TOP, Quaternion({ theta, phi, alpha }) * (Vector3d(-1, 1, 1) * 10)); + tpc.SetVertex(TPC::BACK | TPC::LEFT | TPC::BOTTOM, Quaternion({ theta, phi, alpha }) * (Vector3d(-1, -1, -1) * 10)); + tpc.SetVertex(TPC::BACK | TPC::LEFT | TPC::TOP, Quaternion({ theta, phi, alpha }) * (Vector3d(-1, 1, -1) * 10)); + tpc.SetVertex(TPC::FRONT | TPC::RIGHT | TPC::BOTTOM, Quaternion({ theta, phi, alpha }) * (Vector3d(1, -1, 1) * 10)); + tpc.SetVertex(TPC::FRONT | TPC::RIGHT | TPC::TOP, Quaternion({ theta, phi, alpha }) * (Vector3d(1, 1, 1) * 10)); + tpc.SetVertex(TPC::BACK | TPC::RIGHT | TPC::BOTTOM, Quaternion({ theta, phi, alpha }) * (Vector3d(1, -1, -1) * 10)); + tpc.SetVertex(TPC::BACK | TPC::RIGHT | TPC::TOP, Quaternion({ theta, phi, alpha }) * (Vector3d(1, 1, -1) * 10)); + + // Verify + // Verify that all are inside + for (const Vector3d& v : knownOutsides) + Assert::IsFalse(tpc.Contains(v)); + } + + return; + } + }; +} diff --git a/Test/Vector2.cpp b/Test/Vector2.cpp new file mode 100644 index 0000000..1c2812d --- /dev/null +++ b/Test/Vector2.cpp @@ -0,0 +1,934 @@ +#include "CppUnitTest.h" +#include "../Eule/Vector2.h" +#include "../Eule/Math.h" +#include "../_TestingUtilities/HandyMacros.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace Vectors +{ + TEST_CLASS(_Vector2) + { + private: + std::mt19937 rng; + + public: + // Constructor + _Vector2() + { + rng = std::mt19937((std::random_device())()); + return; + } + + // Tests if all values are 0 after initialization via default constructor + TEST_METHOD(New_Vector_All_0) + { + Vector2d v2; + + Assert::AreEqual(0.0, v2.x); + Assert::AreEqual(0.0, v2.y); + + return; + } + + // Tests if values can be set via the constructor + TEST_METHOD(Can_Set_Values_Constructor) + { + Vector2d v2(69, 32); + + Assert::AreEqual(69.0, v2.x); + Assert::AreEqual(32.0, v2.y); + + return; + } + + // Tests if values can be set via letters + TEST_METHOD(Can_Set_Values_Letters) + { + Vector2d v2; + v2.x = 69; + v2.y = 32; + + Assert::AreEqual(69.0, v2.x); + Assert::AreEqual(32.0, v2.y); + + return; + } + + // Tests if values can be set via array descriptors + TEST_METHOD(Can_Set_Values_ArrayDescriptor) + { + Vector2d v2; + v2[0] = 69; + v2[1] = 32; + + Assert::AreEqual(69.0, v2.x); + Assert::AreEqual(32.0, v2.y); + + return; + } + + // Tests if values can be set via an initializer list + TEST_METHOD(Can_Set_Values_InitializerList) + { + Vector2d v2 = {69, 32}; + + Assert::AreEqual(69.0, v2.x); + Assert::AreEqual(32.0, v2.y); + + return; + } + + // Tests for vectors copied via the copy constructor to have the same values + TEST_METHOD(Copy_Constructor_Same_Values) + { + Vector2d a(69, 32); + Vector2d b(a); + + Assert::AreEqual(a.x, b.x); + Assert::AreEqual(a.y, b.y); + + return; + } + + // Tests for vectors copied via the equals operator to have the same values + TEST_METHOD(Operator_Equals_Same_Values) + { + Vector2d a(69, 32); + Vector2d b = a; + + Assert::AreEqual(a.x, b.x); + Assert::AreEqual(a.y, b.y); + + return; + } + + // Tests for vectors copied via the copy constructor to be modifyable without modifying the original object + TEST_METHOD(Copy_Constructor_Independent) + { + Vector2d a(69, 32); + Vector2d b(a); + + b.x = 169; + b.y = 132; + + Assert::AreEqual(69.0, a.x); + Assert::AreEqual(32.0, a.y); + + Assert::AreEqual(169.0, b.x); + Assert::AreEqual(132.0, b.y); + + return; + } + + // Tests for vectors copied via the equals operator to be modifyable without modifying the original object + TEST_METHOD(Operator_Equals_Independent) + { + Vector2d a(69, 32); + Vector2d b = a; + + b.x = 169; + b.y = 132; + + Assert::AreEqual(69.0, a.x); + Assert::AreEqual(32.0, a.y); + + Assert::AreEqual(169.0, b.x); + Assert::AreEqual(132.0, b.y); + + return; + } + + // Tests if the dot product between two vectors angled 90 degrees from one another is 0. It should by definition be 0! + // Dot products are commutative, so we'll check both directions. + TEST_METHOD(DotProduct_90deg) + { + // Test 1000 times + for (std::size_t i = 0; i < 100; i++) + { + // The length of the vectors should not matter. Only the angle should. + // Let's test that! + Vector2d a = Vector2d(1, 0) * (rng() % 6969 + 1.0); + Vector2d b = Vector2d(0, 1) * (rng() % 6969 + 1.0); + + std::wstringstream wss; + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::AreEqual(0.0, a.DotProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.DotProduct(a), wss.str().c_str()); + } + + return; + } + + // Test if the dot product is positive for two vectors angled less than 90 degrees from another + // Dot products are commutative, so we'll check both directions. + TEST_METHOD(DotProduct_LessThan90deg) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // The length of the vectors should not matter. Only the angle should. + // Let's test that! + Vector2d a = Vector2d(1, 1.0 / (rng() % 100)) * (rng() % 6969 + 1.0); // Don't allow the scalar to become 0 + Vector2d b = Vector2d(1.0 / (rng() % 100), 1) * (rng() % 6969 + 1.0); + + + std::wstringstream wss; + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::IsTrue(a.DotProduct(b) > 0, wss.str().c_str()); + Assert::IsTrue(b.DotProduct(a) > 0, wss.str().c_str()); + } + + return; + } + + // Test if the dot product is negative for two vectors angled greater than 90 degrees from another + // Dot products are commutative, so we'll check both directions. + TEST_METHOD(DotProduct_GreaterThan90deg) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // The length of the vectors should not matter. Only the angle should. + // Let's test that! + Vector2d a = Vector2d(1, -1.0 / (rng() % 100)) * (rng() % 6969 + 1.0); // Don't allow the scalar to become 0 + Vector2d b = Vector2d(-1.0 / (rng() % 100), 1) * (rng() % 6969 + 1.0); + + std::wstringstream wss; + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::IsTrue(a.DotProduct(b) < 0, wss.str().c_str()); + Assert::IsTrue(b.DotProduct(a) < 0, wss.str().c_str()); + } + + return; + } + + // Tests that the dot product is correct for a known value + TEST_METHOD(DotProduct_Oracle) + { + // Setup + Vector2d a(-99, 199); + Vector2d b(18, -1); + + // Exercise + const double dot = a.DotProduct(b); + + // Verify + Assert::AreEqual(-1981.0, dot); + + return; + } + + // Quick and dirty check if the useless int-method is working + TEST_METHOD(DotProduct_Dirty_Int) + { + Vector2i a; + Vector2i b; + std::wstringstream wss; + + // 90 deg + a = {0, 10}; + b = {10, 0}; + wss.str(L""); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::AreEqual(0.0, a.DotProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.DotProduct(a), wss.str().c_str()); + + // < 90 deg + a = { 7, 10 }; + b = { 10, 1 }; + wss.str(L""); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::IsTrue(a.DotProduct(b) > 0.0, wss.str().c_str()); + Assert::IsTrue(b.DotProduct(a) > 0.0, wss.str().c_str()); + + // > 90 deg + a = { -3, 10 }; + b = { 10, -4 }; + wss.str(L""); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::IsTrue(a.DotProduct(b) < 0.0, wss.str().c_str()); + Assert::IsTrue(b.DotProduct(a) < 0.0, wss.str().c_str()); + + return; + } + + // Tests if the cross product of two vectors of the exact opposite direction is 0 + TEST_METHOD(CrossProduct_Opposite_Direction) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE / 1000.0; + double y = LARGE_RAND_DOUBLE / 1000.0; + + // Vector length should not matter, so randomize it + // In this case, they are allowed to be of length 0 + // Don't scale it up too much to avoid failure due to floating point inaccuracy + Vector2d a = Vector2d( x, y) * (LARGE_RAND_DOUBLE / 1000.0); + Vector2d b = Vector2d(-x, -y) * (LARGE_RAND_DOUBLE / 1000.0); + + std::wstringstream wss; + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Math::Similar(a.CrossProduct(b), 0.0, 10), wss.str().c_str()); + } + + return; + } + + // Tests if the cross product of two vectors of the exact same direction is 0 + TEST_METHOD(CrossProduct_Same_Direction) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE / 1000.0; + double y = LARGE_RAND_DOUBLE / 1000.0; + + // Vector length should not matter, so randomize it + // In this case, they are allowed to be of length 0 + // Don't scale it up too much to avoid failure due to floating point inaccuracy + Vector2d a = Vector2d(x, y) * (LARGE_RAND_DOUBLE / 1000.0); + Vector2d b = Vector2d(x, y) * (LARGE_RAND_DOUBLE / 1000.0); + + std::wstringstream wss; + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Math::Similar(a.CrossProduct(b), 0.0, 10), wss.str().c_str()); + } + + return; + } + + // Tests for the cross product to be positive, if vector b is to the left of a + TEST_METHOD(CrossProduct_BToTheLeft) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + if (x == 0) x++; + if (y == 0) y++; + + // Vector length should not matter, so randomize it + Vector2d a = Vector2d(x, y) * (rng() % 6969 + 1.0); + Vector2d b = Vector2d(x - (rng() % 6969 + 1.0), y) * (rng() % 6969 + 1.0); + + std::wstringstream wss; + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(a.CrossProduct(b) > 0, wss.str().c_str()); + } + + return; + } + + // Tests for the cross product to be negative, if vector b is to the left of a + TEST_METHOD(CrossProduct_BToTheRight) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + if (x == 0) x++; + if (y == 0) y++; + + // Vector length should not matter, so randomize it + Vector2d a = Vector2d(x, y) * (rng() % 6969 + 1.0); + Vector2d b = Vector2d(x + (rng() % 6969 + 1.0), y) * (rng() % 6969 + 1.0); + + std::wstringstream wss; + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(a.CrossProduct(b) < 0, wss.str().c_str()); + } + + return; + } + + // Quick and dirty check if the useless int-method is working + TEST_METHOD(CrossProduct_Dirty_Int) + { + Vector2i a; + Vector2i b; + std::wstringstream wss; + + // Same direction + a = { 10, 0 }; + b = { 10, 0 }; + wss.str(L""); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::AreEqual(0.0, a.CrossProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.CrossProduct(a), wss.str().c_str()); + + // Opposite direction + a = { -10, 0 }; + b = { 10, 0 }; + wss.str(L""); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::AreEqual(0.0, a.CrossProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.CrossProduct(a), wss.str().c_str()); + + // B to the left + a = { 0, 10 }; + b = { -5, 10 }; + wss.str(L""); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(a.CrossProduct(b) > 0.0, wss.str().c_str()); + + // B to the right + a = { 0, 10 }; + b = { 17, 10 }; + wss.str(L""); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(a.CrossProduct(b) < 0.0, wss.str().c_str()); + + return; + } + + // Tests the SqrMagnitude method to work as expected with random numbers + TEST_METHOD(SqrMagnitude) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = (double)(rng() % 1000) - 500.0; + double y = (double)(rng() % 1000) - 500.0; + double expected = x*x + y*y; + + Assert::AreEqual(expected, Vector2d(x, y).SqrMagnitude()); + } + + return; + } + + // Checks if the int method is working + TEST_METHOD(SqrMagnitude_Int) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + int x = LARGE_RAND_INT; + int y = LARGE_RAND_INT; + int expected = x*x + y*y; + + Assert::IsTrue(Math::Similar((double)expected, Vector2i(x, y).SqrMagnitude())); + } + + return; + } + + // Tests for the length of the vector (0,0) being 0 + TEST_METHOD(Magnitude_Is_0_On_Vec0) + { + Assert::AreEqual(0.0, Vector2d(0, 0).Magnitude()); + return; + } + + // Tests for a vector of a known length to actually return that + TEST_METHOD(Magnitude_One_Axis_X) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = (double)(rng() % 1000) - 500.0; + Vector2d vec(x, 0); + Assert::IsTrue(Math::Similar(abs(x), vec.Magnitude())); + } + + return; + } + + // Tests for a vector of a known length to actually return that + TEST_METHOD(Magnitude_One_Axis_Y) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double y = (double)(rng() % 1000) - 500.0; + Vector2d vec(0, y); + Assert::IsTrue(Math::Similar(abs(y), vec.Magnitude())); + } + + return; + } + + // Tests for a known result + TEST_METHOD(Magnitude) + { + // Ya'll got more of 'dem digits? + Assert::AreEqual(204.02205763103165736538358032703399658203125, Vector2d(192, -69).Magnitude()); + return; + } + + // Tests for expected lerp result 0.00 + TEST_METHOD(Lerp_000) + { + Vector2d a(100, 1000); + Vector2d b(200, 4000); + Vector2d res = a.Lerp(b, 0.00); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(a == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 0.25 + TEST_METHOD(Lerp_025) + { + Vector2d a(100, 1000); + Vector2d b(200, 4000); + Vector2d res = a.Lerp(b, 0.25); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(Vector2d(125, 1750) == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 0.50 + TEST_METHOD(Lerp_050) + { + Vector2d a(100, 1000); + Vector2d b(200, 4000); + Vector2d res = a.Lerp(b, 0.50); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(Vector2d(150, 2500) == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 0.75 + TEST_METHOD(Lerp_075) + { + Vector2d a(100, 1000); + Vector2d b(200, 4000); + Vector2d res = a.Lerp(b, 0.75); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(Vector2d(175, 3250) == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 1.00 + TEST_METHOD(Lerp_100) + { + Vector2d a(100, 1000); + Vector2d b(200, 4000); + Vector2d res = a.Lerp(b, 1.00); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(b == res, wss.str().c_str()); + return; + } + + // Tests lerpself + TEST_METHOD(LerpSelf) + { + Vector2d a(100, 1000); + Vector2d b(200, 4000); + + a.LerpSelf(b, 0.75); + + std::wstringstream wss; + wss << a; + Assert::IsTrue(Vector2d(175, 3250) == a, wss.str().c_str()); + return; + } + + // Tests if an input vector of length 0 is handled correctly by the normalize method + TEST_METHOD(Normalize_Length_Before_Is_0) + { + Vector2d vec(0, 0); + Assert::AreEqual(0.0, vec.Normalize().Magnitude()); + return; + } + + // Tests for any normalized vector to be of length 1 + TEST_METHOD(Normalize_Length_Is_1) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + if (x == 0) x++; + if (y == 0) y++; + + Vector2d vec(x, y); + + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Math::Similar(vec.Normalize().Magnitude(), 1.0), wss.str().c_str()); // Account for floating point inaccuracy + } + + return; + } + + // Tests the normalize method with known values + TEST_METHOD(Normalize_Oracle) + { + // Setup + Vector2d v(3.2, -5.3); + + // Exercise + v.NormalizeSelf(); + + // Verify + Vector2d expected(0.51686909903, -0.85606444527); + Assert::IsTrue(v.Similar(expected)); + } + + // Tests for a normalized vector to still point in the exact same direction + TEST_METHOD(Normalize_Direction_Stays_Unaffected) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + + Vector2d vec(x, y); + + // Prevent a vector of length 0 going in + if (vec.SqrMagnitude() == 0) + vec.x++; + + Vector2d vec_n(x, y); + vec_n = vec_n.Normalize(); + + std::wstringstream wss; + wss << vec << L" | " << vec_n; + + // Both vectors should still point in the same direction! + Assert::IsTrue( + (vec.DotProduct(vec_n) > 0) && // Roughly same direction + (Math::Similar(vec_n.CrossProduct(vec), 0.0)), // Both vectors align + wss.str().c_str()); + } + return; + } + + // Kinda dumb method, but ok lol + // DON'T NORMALIZE INT-VECTORS WHAT IS WRONG WITH YOU + TEST_METHOD(Normalized_Int_Vector_Is_0) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + int x = LARGE_RAND_INT; + int y = LARGE_RAND_INT; + + Vector2i vec(x, y); + + vec.NormalizeSelf(); + + std::wstringstream wss; + wss << vec; + Assert::AreEqual(0.0, vec.Magnitude(), wss.str().c_str()); + } + } + + // Tests that NormalizeSelf() results in the same as Normalize() + TEST_METHOD(NormalizeSelf_IsSameAs_Normalize) + { + // Run test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + Vector2d vec(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE); + + Vector2d nVec = vec.Normalize(); + vec.NormalizeSelf(); + + Assert::IsTrue(nVec == vec); + } + + return; + } + + // Tests for the VectorScale() method to work + TEST_METHOD(VectorScale) + { + // Run test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + const double ax = LARGE_RAND_DOUBLE; + const double ay = LARGE_RAND_DOUBLE; + const double bx = LARGE_RAND_DOUBLE; + const double by = LARGE_RAND_DOUBLE; + + Vector2d a(ax, ay); + Vector2d b(bx, by); + + Vector2d target( + ax * bx, + ay * by + ); + + Assert::IsTrue(a.VectorScale(b) == target); + } + + return; + } + + // Tests for operator- (unary) to work + TEST_METHOD(Operator_Unary_Negative) + { + Vector2d v(29, -5); + + Assert::IsTrue(Vector2d(-29, 5) == -v); + + return; + } + + // Tests for operator+ to work as expected + TEST_METHOD(Operator_Add) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + + Vector2d a(ax, ay); + Vector2d b(bx, by); + + Assert::IsTrue(Vector2d(ax+bx, ay+by) == a+b); + } + + return; + } + + // Tests for operator+= to work as expected + TEST_METHOD(Operator_Add_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + + Vector2d a(ax, ay); + a += Vector2d(bx, by); + + Assert::IsTrue(Vector2d(ax + bx, ay + by) == a); + } + + return; + } + + // Tests for operator- to work as expected + TEST_METHOD(Operator_Sub) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + + Vector2d a(ax, ay); + Vector2d b(bx, by); + + Assert::IsTrue(Vector2d(ax - bx, ay - by) == a - b); + } + + return; + } + + // Tests for operator-= to work as expected + TEST_METHOD(Operator_Sub_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + + Vector2d a(ax, ay); + a -= Vector2d(bx, by); + + Assert::IsTrue(Vector2d(ax - bx, ay - by) == a); + } + + return; + } + + // Tests for operator* to work as expected + TEST_METHOD(Operator_Mult) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector2d a(x, y); + + Assert::IsTrue(Vector2d(x * scalar, y * scalar) == a * scalar); + } + + return; + } + + // Tests for operator*= to work as expected + TEST_METHOD(Operator_Mult_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector2d a(x, y); + a *= scalar; + + Assert::IsTrue(Vector2d(x * scalar, y * scalar) == a); + } + + return; + } + + // Tests for operator/ to work as expected + TEST_METHOD(Operator_Div) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector2d a(x, y); + + Assert::IsTrue(Vector2d(x / scalar, y / scalar) == a / scalar); + } + + return; + } + + // Tests for operator/= to work as expected + TEST_METHOD(Operator_Div_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector2d a(x, y); + a /= scalar; + + Assert::IsTrue(Vector2d(x / scalar, y / scalar) == a); + } + + return; + } + + // Tests for operator== to work as expected + TEST_METHOD(Operator_Compare_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = (rng() % 10) - 5; + double ay = (rng() % 10) - 5; + double bx = (rng() % 10) - 5; + double by = (rng() % 10) - 5; + + Vector2d a(ax, ay); + Vector2d b(bx, by); + + Assert::IsTrue( + ((ax == bx) && (ay == by)) == + (a == b) + ); + } + + return; + } + + // Tests for operator!= to work as expected + TEST_METHOD(Operator_Not_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = (rng() % 10) - 5; + double ay = (rng() % 10) - 5; + double bx = (rng() % 10) - 5; + double by = (rng() % 10) - 5; + + Vector2d a(ax, ay); + Vector2d b(bx, by); + + Assert::IsTrue( + ((ax != bx) || (ay != by)) == + (a != b) + ); + } + + return; + } + + // Tests loose comparison via Vector2d::Similar -> true + TEST_METHOD(Similar_True) + { + Assert::IsTrue( + Vector2d(0.00000000000000000000001, -6.6666666666666666666666666666).Similar( + Vector2d(0, -6.666666667) + )); + return; + } + + // Tests loose comparison via Vector2d::Similar -> false + TEST_METHOD(Similar_False) + { + Assert::IsFalse( + Vector2d(0.00000000000000000000001, -6.6666666666666666666666666666).Similar( + Vector2d(0.1, -6.7) + )); + return; + } + + // Tests that the move constructor works + TEST_METHOD(Move_Constructor) + { + Vector2d a(1, 2); + Vector2d b(std::move(a)); + + Assert::AreEqual(b.x, 1.0); + Assert::AreEqual(b.y, 2.0); + + return; + } + + // Tests that the move operator works + TEST_METHOD(Move_Operator) + { + Vector2d a(1, 2); + Vector2d b = std::move(a); + + Assert::AreEqual(b.x, 1.0); + Assert::AreEqual(b.y, 2.0); + + return; + } + }; +} diff --git a/Test/Vector3.cpp b/Test/Vector3.cpp new file mode 100644 index 0000000..32d9d2b --- /dev/null +++ b/Test/Vector3.cpp @@ -0,0 +1,1553 @@ +#include "CppUnitTest.h" +#include "../Eule/Vector3.h" +#include "../Eule/Math.h" +#include "../_TestingUtilities/HandyMacros.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace Vectors +{ + TEST_CLASS(_Vector3) + { + private: + std::mt19937 rng; + + public: + // Constructor + _Vector3() + { + rng = std::mt19937((std::random_device())()); + return; + } + + // Tests if all values are 0 after initialization via default constructor + TEST_METHOD(New_Vector_All_0) + { + Vector3d v3; + + Assert::AreEqual(0.0, v3.x); + Assert::AreEqual(0.0, v3.y); + Assert::AreEqual(0.0, v3.z); + + return; + } + + // Tests if values can be set via the constructor + TEST_METHOD(Can_Set_Values_Constructor) + { + Vector3d v3(69, 32, 16); + + Assert::AreEqual(69.0, v3.x); + Assert::AreEqual(32.0, v3.y); + Assert::AreEqual(16.0, v3.z); + + return; + } + + // Tests if values can be set via letters + TEST_METHOD(Can_Set_Values_Letters) + { + Vector3d v3; + v3.x = 69; + v3.y = 32; + v3.z = 16; + + Assert::AreEqual(69.0, v3.x); + Assert::AreEqual(32.0, v3.y); + Assert::AreEqual(16.0, v3.z); + + return; + } + + // Tests if values can be set via array descriptors + TEST_METHOD(Can_Set_Values_ArrayDescriptor) + { + Vector3d v3; + v3[0] = 69; + v3[1] = 32; + v3[2] = 16; + + Assert::AreEqual(69.0, v3.x); + Assert::AreEqual(32.0, v3.y); + Assert::AreEqual(16.0, v3.z); + + return; + } + + // Tests if values can be set via an initializer list + TEST_METHOD(Can_Set_Values_InitializerList) + { + Vector3d v3 = { 69, 32, 16 }; + + Assert::AreEqual(69.0, v3.x); + Assert::AreEqual(32.0, v3.y); + Assert::AreEqual(16.0, v3.z); + + return; + } + + // Tests for vectors copied via the copy constructor to have the same values + TEST_METHOD(Copy_Constructor_Same_Values) + { + Vector3d a(69, 32, 16); + Vector3d b(a); + + Assert::AreEqual(a.x, b.x); + Assert::AreEqual(a.y, b.y); + Assert::AreEqual(a.z, b.z); + + return; + } + + // Tests for vectors copied via the equals operator to have the same values + TEST_METHOD(Operator_Equals_Same_Values) + { + Vector3d a(69, 32, 16); + Vector3d b = a; + + Assert::AreEqual(a.x, b.x); + Assert::AreEqual(a.y, b.y); + Assert::AreEqual(a.z, b.z); + + return; + } + + // Tests for vectors copied via the copy constructor to be modifyable without modifying the original object + TEST_METHOD(Copy_Constructor_Independent) + { + Vector3d a(69, 32, 16); + Vector3d b(a); + + b.x = 169; + b.y = 132; + b.z = 116; + + Assert::AreEqual(69.0, a.x); + Assert::AreEqual(32.0, a.y); + Assert::AreEqual(16.0, a.z); + + Assert::AreEqual(169.0, b.x); + Assert::AreEqual(132.0, b.y); + Assert::AreEqual(116.0, b.z); + + return; + } + + // Tests for vectors copied via the equals operator to be modifyable without modifying the original object + TEST_METHOD(Operator_Equals_Independent) + { + Vector3d a(69, 32, 16); + Vector3d b = a; + + b.x = 169; + b.y = 132; + b.z = 116; + + Assert::AreEqual(69.0, a.x); + Assert::AreEqual(32.0, a.y); + Assert::AreEqual(16.0, a.z); + + Assert::AreEqual(169.0, b.x); + Assert::AreEqual(132.0, b.y); + Assert::AreEqual(116.0, b.z); + + return; + } + + // Tests if the dot product between two vectors angled 90 degrees from one another is 0. It should by definition be 0! + // Dot products are commutative, so we'll check both directions. + // This test tests all possible 90 degree setups with 1000x random lengths + TEST_METHOD(DotProduct_90deg) + { + // Test 1000 times + for (std::size_t i = 0; i < 100; i++) + { + // The length of the vectors should not matter. Only the angle should. + // Let's test that! + Vector3d a = Vector3d(1, 0, 0) * (rng() % 6969 + 1.0); + Vector3d b = Vector3d(0, 1, 0) * (rng() % 6969 + 1.0); + + std::wstringstream wss; + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::AreEqual(0.0, a.DotProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.DotProduct(a), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(0, 1, 0) * (rng() % 6969 + 1.0); + b = Vector3d(1, 0, 0) * (rng() % 6969 + 1.0); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::AreEqual(0.0, a.DotProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.DotProduct(a), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(0, 1, 0) * (rng() % 6969 + 1.0); + b = Vector3d(0, 0, 1) * (rng() % 6969 + 1.0); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::AreEqual(0.0, a.DotProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.DotProduct(a), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(0, 0, 1) * (rng() % 6969 + 1.0); + b = Vector3d(0, 1, 0) * (rng() % 6969 + 1.0); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::AreEqual(0.0, a.DotProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.DotProduct(a), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(1, 0, 0) * (rng() % 6969 + 1.0); + b = Vector3d(0, 0, 1) * (rng() % 6969 + 1.0); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::AreEqual(0.0, a.DotProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.DotProduct(a), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(0, 0, 1) * (rng() % 6969 + 1.0); + b = Vector3d(1, 0, 0) * (rng() % 6969 + 1.0); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::AreEqual(0.0, a.DotProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.DotProduct(a), wss.str().c_str()); + } + + return; + } + + // Test if the dot product is positive for two vectors angled less than 90 degrees from another + // Dot products are commutative, so we'll check both directions. + TEST_METHOD(DotProduct_LessThan90deg) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // The length of the vectors should not matter. Only the angle should. + // Let's test that! + Vector3d a = Vector3d(1, 1.0 / (rng() % 100), 69) * (rng() % 6969 + 1.0); // Don't allow the scalar to become 0 + Vector3d b = Vector3d(1.0 / (rng() % 100), 1, 69) * (rng() % 6969 + 1.0); + + + std::wstringstream wss; + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::IsTrue(a.DotProduct(b) > 0, wss.str().c_str()); + Assert::IsTrue(b.DotProduct(a) > 0, wss.str().c_str()); + } + + return; + } + + // Test if the dot product is negative for two vectors angled greater than 90 degrees from another + // Dot products are commutative, so we'll check both directions. + TEST_METHOD(DotProduct_GreaterThan90deg) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // The length of the vectors should not matter. Only the angle should. + // Let's test that! + Vector3d a = Vector3d(1, -1.0 / (rng() % 100), 69) * (rng() % 6969 + 1.0); // Don't allow the scalar to become 0 + Vector3d b = Vector3d(-1.0 / (rng() % 100), 1, -69) * (rng() % 6969 + 1.0); + + std::wstringstream wss; + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::IsTrue(a.DotProduct(b) < 0, wss.str().c_str()); + Assert::IsTrue(b.DotProduct(a) < 0, wss.str().c_str()); + } + + return; + } + + // Tests that the dot product is correct for a known value + TEST_METHOD(DotProduct_Oracle) + { + // Setup + Vector3d a(-99, 199, -32); + Vector3d b(18, -1, -21); + + // Exercise + const double dot = a.DotProduct(b); + + // Verify + Assert::AreEqual(-1309.0, dot); + + return; + } + + // Quick and dirty check if the useless int-method is working + TEST_METHOD(DotProduct_Dirty_Int) + { + Vector3i a; + Vector3i b; + std::wstringstream wss; + + // 90 deg + a = { 0, 10, 0 }; + b = { 10, 0, 0 }; + wss.str(L""); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::AreEqual(0.0, a.DotProduct(b), wss.str().c_str()); + Assert::AreEqual(0.0, b.DotProduct(a), wss.str().c_str()); + + // < 90 deg + a = { 7, 10, 10 }; + b = { 10, 1, 10 }; + wss.str(L""); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::IsTrue(a.DotProduct(b) > 0.0, wss.str().c_str()); + Assert::IsTrue(b.DotProduct(a) > 0.0, wss.str().c_str()); + + // > 90 deg + a = { -3, 10, -10 }; + b = { 10, -4, 10 }; + wss.str(L""); + wss << a << L" DOT " << b << L" = " << a.DotProduct(b) << std::endl; + Assert::IsTrue(a.DotProduct(b) < 0.0, wss.str().c_str()); + Assert::IsTrue(b.DotProduct(a) < 0.0, wss.str().c_str()); + + return; + } + + // Tests for the cross product between the same vector being 0 + TEST_METHOD(CrossProduct_Same_Vector_Is_0) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + + Vector3d a(x, y, z); + + std::wstringstream wss; + wss << a << L" CROSS " << a << L" = " << a.CrossProduct(a) << std::endl; + Assert::IsTrue(Vector3d(0,0,0) == a.CrossProduct(a), wss.str().c_str()); + } + + return; + } + + // Tests for the cross product between opposite vectors being 0 + TEST_METHOD(CrossProduct_Opposite_Vector_Is_0) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + + Vector3d a(x, y, z); + Vector3d b(-x, -y, -z); + + std::wstringstream wss; + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, 0, 0) == a.CrossProduct(b), wss.str().c_str()); + } + + return; + } + + // Tests for known values + TEST_METHOD(CrossProduct_KnownValues) + { + Vector3d a; + Vector3d b; + + std::wstringstream wss; + + wss.str(L""); + a = Vector3d(1, 0, 0); + b = Vector3d(0, 0, 1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -1, 0) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(-1, 0, 0); + b = Vector3d(0, 0, -1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -1, 0) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(1, 0, 0); + b = Vector3d(0, 0, -1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, 1, 0) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(1, 0, 0); + b = Vector3d(0, 1, 0); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, 0, 1) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(1, 0, 0); + b = Vector3d(1, 0, 1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -1, 0) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(1, 0, 0); + b = Vector3d(1, 1, 1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -1, 1) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3d(3, -1, -3); + b = Vector3d(-1, 1, 3); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -6, 2) == a.CrossProduct(b), wss.str().c_str()); + + return; + } + + // Tests for known values, but with int vectors + TEST_METHOD(CrossProduct_KnownValues_Int) + { + Vector3i a; + Vector3i b; + + std::wstringstream wss; + + wss.str(L""); + a = Vector3i(1, 0, 0); + b = Vector3i(0, 0, 1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -1, 0) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3i(-1, 0, 0); + b = Vector3i(0, 0, -1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -1, 0) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3i(1, 0, 0); + b = Vector3i(0, 0, -1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, 1, 0) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3i(1, 0, 0); + b = Vector3i(0, 1, 0); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, 0, 1) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3i(1, 0, 0); + b = Vector3i(1, 0, 1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -1, 0) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3i(1, 0, 0); + b = Vector3i(1, 1, 1); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -1, 1) == a.CrossProduct(b), wss.str().c_str()); + + wss.str(L""); + a = Vector3i(3, -1, -3); + b = Vector3i(-1, 1, 3); + wss << a << L" CROSS " << b << L" = " << a.CrossProduct(b) << std::endl; + Assert::IsTrue(Vector3d(0, -6, 2) == a.CrossProduct(b), wss.str().c_str()); + + return; + } + + // Tests the SqrMagnitude method to work as expected with random numbers + TEST_METHOD(SqrMagnitude) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = (double)(rng() % 1000) - 500.0; // Too large numbers would get unaccurate decimals when using intrinsics. + double y = (double)(rng() % 1000) - 500.0; + double z = (double)(rng() % 1000) - 500.0; + double expected = x*x + y*y + z*z; + + Assert::IsTrue(Math::Similar(expected, Vector3d(x, y, z).SqrMagnitude())); + } + + return; + } + + // Tests the SqrMagnitude method to work as expected with random numbers, but with an int-vector + TEST_METHOD(SqrMagnitude_Int) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + int x = LARGE_RAND_INT; + int y = LARGE_RAND_INT; + int z = LARGE_RAND_INT; + int expected = x*x + y*y + z*z; + + Assert::AreEqual((double)expected, Vector3i(x, y, z).SqrMagnitude()); + } + + return; + } + + // Tests for the length of the vector (0,0,0) being 0 + TEST_METHOD(Magnitude_Is_0_On_Vec0) + { + Assert::AreEqual(0.0, Vector3d(0, 0, 0).Magnitude()); + return; + } + + // Tests for a vector of a known length to actually return that + TEST_METHOD(Magnitude_One_Axis_X) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = (double)(rng() % 1000) - 500.0; // Too large numbers would get unaccurate decimals when using intrinsics. + Vector3d vec(x, 0, 0); + + std::wstringstream wss; + wss << std::endl << std::setprecision(20) + << "Actual: " << vec.Magnitude() << std::endl + << "Expected: " << x << std::endl; + Assert::IsTrue(Math::Similar(abs(x), vec.Magnitude()), wss.str().c_str()); + } + + return; + } + + // Tests for a vector of a known length to actually return that + TEST_METHOD(Magnitude_One_Axis_Y) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double y = (double)(rng() % 1000) - 500.0; // Too large numbers would get unaccurate decimals when using intrinsics. + Vector3d vec(0, y, 0); + + std::wstringstream wss; + wss << std::endl << std::setprecision(20) + << "Actual: " << vec.Magnitude() << std::endl + << "Expected: " << y << std::endl; + Assert::IsTrue(Math::Similar(abs(y), vec.Magnitude()), wss.str().c_str()); + } + + return; + } + + // Tests for a vector of a known length to actually return that + TEST_METHOD(Magnitude_One_Axis_Z) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double z = (double)(rng() % 1000) - 500.0; // Too large numbers would get unaccurate decimals when using intrinsics. + Vector3d vec(0, 0, z); + + std::wstringstream wss; + wss << std::endl << std::setprecision(20) + << "Actual: " << vec.Magnitude() << std::endl + << "Expected: " << z << std::endl; + Assert::IsTrue(Math::Similar(abs(z), vec.Magnitude()), wss.str().c_str()); + } + + return; + } + + // Tests for a known result + TEST_METHOD(Magnitude) + { + // Ya'll got more of 'dem digits? + Assert::AreEqual(426.14786166306174663986894302070140838623046875, Vector3d(69, -420, 21).Magnitude()); + return; + } + + // Tests for expected lerp result 0.00 + TEST_METHOD(Lerp_000) + { + Vector3d a(100, 1000, 10); + Vector3d b(200, 4000, 100); + Vector3d res = a.Lerp(b, 0.00); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(a == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 0.25 + TEST_METHOD(Lerp_025) + { + Vector3d a(100, 1000, 10); + Vector3d b(200, 4000, 100); + Vector3d res = a.Lerp(b, 0.25); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(Vector3d(125, 1750, 32.5) == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 0.50 + TEST_METHOD(Lerp_050) + { + Vector3d a(100, 1000, 10); + Vector3d b(200, 4000, 100); + Vector3d res = a.Lerp(b, 0.50); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(Vector3d(150, 2500, 55) == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 0.75 + TEST_METHOD(Lerp_075) + { + Vector3d a(100, 1000, 10); + Vector3d b(200, 4000, 100); + Vector3d res = a.Lerp(b, 0.75); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(Vector3d(175, 3250, 77.5) == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 1.00 + TEST_METHOD(Lerp_100) + { + Vector3d a(100, 1000, 10); + Vector3d b(200, 4000, 100); + Vector3d res = a.Lerp(b, 1.00); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(b == res, wss.str().c_str()); + return; + } + + // Tests lerpself + TEST_METHOD(LerpSelf) + { + Vector3d a(100, 1000, 10); + Vector3d b(200, 4000, 100); + + a.LerpSelf(b, 0.75); + + std::wstringstream wss; + wss << a; + Assert::IsTrue(Vector3d(175, 3250, 77.5) == a, wss.str().c_str()); + return; + } + + // Tests if an input vector of length 0 is handled correctly by the normalize method + TEST_METHOD(Normalize_Length_Before_Is_0) + { + Vector3d vec(0, 0, 0); + vec.NormalizeSelf(); + Assert::AreEqual(0.0, vec.Magnitude()); + return; + } + + // Tests for any normalized vector to be of length 1 + TEST_METHOD(Normalize_Length_Is_1) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + + Vector3d vec(x, y, z); + + // Prevent a vector of length 0 going in + if (vec.SqrMagnitude() == 0) + vec.x++; + + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Math::Similar(vec.Normalize().Magnitude(), 1.0), wss.str().c_str()); // Account for floating point inaccuracy + } + + return; + } + + // Tests the normalize method with known values + TEST_METHOD(Normalize_Oracle) + { + // Setup + Vector3d v(3.2, -5.3, 9.88); + + // Exercise + v.NormalizeSelf(); + + // Verify + Vector3d expected(0.27445384355, -0.45456417839, 0.84737624198); + Assert::IsTrue(v.Similar(expected)); + } + + // Tests for a normalized vector to still point in the exact same direction + TEST_METHOD(Normalize_Direction_Stays_Unaffected) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + if (x == 0) x++; + if (y == 0) y++; + if (z == 0) z++; + + Vector3d vec(x, y, z); + + Vector3d vec_n(x, y, z); + vec_n = vec_n.Normalize(); + + std::wstringstream wss; + wss << vec << L" | " << vec_n; + + // Both vectors should still point in the same direction! + Assert::IsTrue( + (vec.DotProduct(vec_n) > 0) && // Roughly same direction + (Math::Similar(vec_n.CrossProduct(vec).Magnitude(), 0.0)), // Both vectors align + wss.str().c_str()); + } + return; + } + + // Kinda dumb method, but ok lol + // DON'T NORMALIZE INT-VECTORS WHAT IS WRONG WITH YOU + TEST_METHOD(Normalized_Int_Vector_Is_0) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + int x = LARGE_RAND_INT; + int y = LARGE_RAND_INT; + int z = LARGE_RAND_INT; + + Vector3i vec(x, y, z); + + vec.NormalizeSelf(); + + std::wstringstream wss; + wss << vec; + Assert::AreEqual(0.0, vec.Magnitude(), wss.str().c_str()); + } + } + + // Tests that NormalizeSelf() results in the same as Normalize() + TEST_METHOD(NormalizeSelf_IsSameAs_Normalize) + { + // Run test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + Vector3d vec(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE); + + Vector3d nVec = vec.Normalize(); + vec.NormalizeSelf(); + + Assert::IsTrue(nVec == vec); + } + + return; + } + + // Tests for the VectorScale() method to work + TEST_METHOD(VectorScale) + { + // Run test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + const double ax = LARGE_RAND_DOUBLE; + const double ay = LARGE_RAND_DOUBLE; + const double az = LARGE_RAND_DOUBLE; + const double bx = LARGE_RAND_DOUBLE; + const double by = LARGE_RAND_DOUBLE; + const double bz = LARGE_RAND_DOUBLE; + + Vector3d a(ax, ay, az); + Vector3d b(bx, by, bz); + + Vector3d target( + ax * bx, + ay * by, + az * bz + ); + + Assert::IsTrue(a.VectorScale(b) == target); + } + + return; + } + + // Tests for operator- (unary) to work + TEST_METHOD(Operator_Unary_Negative) + { + Vector3d v(29, -5, 35); + + Assert::IsTrue(Vector3d(-29, 5, -35) == -v); + + return; + } + + // Tests for operator+ to work as expected + TEST_METHOD(Operator_Add) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + + Vector3d a(ax, ay, az); + Vector3d b(bx, by, bz); + + Assert::IsTrue(Vector3d(ax + bx, ay + by, az + bz) == a + b); + } + + return; + } + + // Tests for operator+= to work as expected + TEST_METHOD(Operator_Add_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + + Vector3d a(ax, ay, az); + a += Vector3d(bx, by, bz); + + Assert::IsTrue(Vector3d(ax + bx, ay + by, az + bz) == a); + } + + return; + } + + // Tests for operator- to work as expected + TEST_METHOD(Operator_Sub) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + + Vector3d a(ax, ay, az); + Vector3d b(bx, by, bz); + + Assert::IsTrue(Vector3d(ax - bx, ay - by, az - bz) == a - b); + } + + return; + } + + // Tests for operator-= to work as expected + TEST_METHOD(Operator_Sub_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + + Vector3d a(ax, ay, az); + a -= Vector3d(bx, by, bz); + + Assert::IsTrue(Vector3d(ax - bx, ay - by, az - bz) == a); + } + + return; + } + + // Tests for operator* to work as expected + TEST_METHOD(Operator_Mult) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector3d a(x, y, z); + + Assert::IsTrue(Vector3d(x * scalar, y * scalar, z * scalar) == a * scalar); + } + + return; + } + + // Tests for operator*= to work as expected + TEST_METHOD(Operator_Mult_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector3d a(x, y, z); + a *= scalar; + + Assert::IsTrue(Vector3d(x * scalar, y * scalar, z * scalar) == a); + } + + return; + } + + // Tests for operator/ to work as expected + TEST_METHOD(Operator_Div) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector3d a(x, y, z); + + Assert::IsTrue(Vector3d(x / scalar, y / scalar, z / scalar) == a / scalar); + } + + return; + } + + // Tests for operator/= to work as expected + TEST_METHOD(Operator_Div_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector3d a(x, y, z); + a /= scalar; + + Assert::IsTrue(Vector3d(x / scalar, y / scalar, z / scalar) == a); + } + + return; + } + + // Tests for operator== to work as expected + TEST_METHOD(Operator_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 10000; i++) + { + double ax = (rng() % 10) - 5; + double ay = (rng() % 10) - 5; + double az = (rng() % 10) - 5; + double bx = (rng() % 10) - 5; + double by = (rng() % 10) - 5; + double bz = (rng() % 10) - 5; + + Vector3d a(ax, ay, az); + Vector3d b(bx, by, bz); + + Assert::IsTrue( + ((ax == bx) && (ay == by) && (az == bz)) == + (a == b) + ); + } + + return; + } + + // Tests for operator!= to work as expected + TEST_METHOD(Operator_Not_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 10000; i++) + { + double ax = (rng() % 10) - 5; + double ay = (rng() % 10) - 5; + double az = (rng() % 10) - 5; + double bx = (rng() % 10) - 5; + double by = (rng() % 10) - 5; + double bz = (rng() % 10) - 5; + + Vector3d a(ax, ay, az); + Vector3d b(bx, by, bz); + + Assert::IsTrue( + ((ax != bx) || (ay != by) || (az != bz)) == + (a != b) + ); + } + + return; + } + + // Tests for matrix multiplication working regarding rotation + TEST_METHOD(MatrixMult_Rotate_Yaw) + { + // Create vector + Vector3d vec(69, 32, 16); + Vector3d originalVec = vec; + + // Create 90deg yaw rotation matrix (Y) + Matrix4x4 mat; + mat[0] = { 0, 0, 1, 0 }; + mat[1] = { 0, 1, 0, 0 }; + mat[2] = { -1, 0, 0, 0 }; + + // Rotate vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << L"Rot #1 " << vec; + Assert::IsTrue(Vector3d(16, 32, -69) == vec, wss.str().c_str()); + + // Rotate again! + vec *= mat; + wss.str(L""); + wss << L"Rot #2 " << vec; + Assert::IsTrue(Vector3d(-69, 32, -16) == vec, wss.str().c_str()); + + // Rotate again! + vec *= mat; + wss.str(L""); + wss << L"Rot #3 " << vec; + Assert::IsTrue(Vector3d(-16, 32, 69) == vec, wss.str().c_str()); + + // Rotate by 90 deg a last time, having completed a 360° rotation. + vec *= mat; + wss.str(L""); + wss << L"Rot #4 " << vec; + Assert::IsTrue(originalVec == vec, wss.str().c_str()); + + return; + } + + // Tests for matrix multiplication working regarding rotation + TEST_METHOD(MatrixMult_Rotate_Roll) + { + // Create vector + Vector3d vec(69, 32, 16); + Vector3d originalVec = vec; + + // Create 90deg roll rotation matrix (Z) + Matrix4x4 mat; + mat[0] = { 0, -1, 0, 0 }; + mat[1] = { 1, 0, 0, 0 }; + mat[2] = { 0, 0, 1, 0 }; + + // Rotate vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << L"Rot #1 " << vec; + Assert::IsTrue(Vector3d(-32, 69, 16) == vec, wss.str().c_str()); + + // Rotate again! + vec *= mat; + wss.str(L""); + wss << L"Rot #2 " << vec; + Assert::IsTrue(Vector3d(-69, -32, 16) == vec, wss.str().c_str()); + + // Rotate again! + vec *= mat; + wss.str(L""); + wss << L"Rot #3 " << vec; + Assert::IsTrue(Vector3d(32, -69, 16) == vec, wss.str().c_str()); + + // Rotate by 90 deg a last time, having completed a 360° rotation. + vec *= mat; + wss.str(L""); + wss << L"Rot #4 " << vec; + Assert::IsTrue(originalVec == vec, wss.str().c_str()); + + return; + } + + // Tests for matrix multiplication working regarding rotation + TEST_METHOD(MatrixMult_Rotate_Pitch) + { + // Create vector + Vector3d vec(69, 32, 16); + Vector3d originalVec = vec; + + // Create 90deg pitch rotation matrix (X) + Matrix4x4 mat; + mat[0] = { 1, 0, 0, 0 }; + mat[1] = { 0, 0, -1, 0 }; + mat[2] = { 0, 1, 0, 0 }; + + // Rotate vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << L"Rot #1 " << vec; + Assert::IsTrue(Vector3d(69, -16, 32) == vec, wss.str().c_str()); + + // Rotate again! + vec *= mat; + wss.str(L""); + wss << L"Rot #2 " << vec; + Assert::IsTrue(Vector3d(69, -32, -16) == vec, wss.str().c_str()); + + // Rotate again! + vec *= mat; + wss.str(L""); + wss << L"Rot #3 " << vec; + Assert::IsTrue(Vector3d(69, 16, -32) == vec, wss.str().c_str()); + + // Rotate by 90 deg a last time, having completed a 360° rotation. + vec *= mat; + wss.str(L""); + wss << L"Rot #4 " << vec; + Assert::IsTrue(originalVec == vec, wss.str().c_str()); + + return; + } + + // Tests if rotating a vector (1,1,1) by (45,45,45) eulers works + TEST_METHOD(MatrixMult_Rotate_Unit_Combined) + { + // Create vector + Vector3d vec(1, 1, 1); + + // Create rotation matrix + Matrix4x4 mat; + mat[0] = { 0.5, -0.1465, 0.8535, 0 }; + mat[1] = { 0.5, 0.8535, -0.1465, 0 }; + mat[2] = { -0.7072, 0.5, 0.5, 0 }; + + // Rotate vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue( + Math::Similar(vec.x, 1.207, 0.001) && + Math::Similar(vec.y, 1.207, 0.001) && + Math::Similar(vec.z, 0.2928, 0.001), + wss.str().c_str()); + + return; + } + + // Tests if rotating a vector (69,32,16) by (45,45,45) eulers works + TEST_METHOD(MatrixMult_Rotate_HalfUnit_Combined) + { + // Create vector + Vector3d vec(69, 32, 16); + + // Create rotation matrix + Matrix4x4 mat; + mat[0] = { 0.5, -0.1465, 0.8535, 0 }; + mat[1] = { 0.5, 0.8535, -0.1465, 0 }; + mat[2] = { -0.7072, 0.5, 0.5, 0 }; + + // Rotate vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue( + Math::Similar(vec.x, 43.468, 0.001) && + Math::Similar(vec.y, 59.468, 0.001) && + Math::Similar(vec.z, -24.7968, 0.001), + wss.str().c_str()); + + return; + } + + // Tests if rotating a vector (69,32,16) by (45,45,45) eulers works + TEST_METHOD(MatrixMult_Rotate_Combined) + { + // Create vector + Vector3d vec(69, 32, 16); + + // Create rotation matrix + Matrix4x4 mat; + mat[0] = { -0.1639, -0.9837, -0.0755, 0 }; + mat[1] = { 0.128, -0.097, 0.987, 0 }; + mat[2] = { -0.9782, 0.152, 0.1417, 0 }; + + // Rotate vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue( + Math::Similar(vec.x, -43.9955, 0.001) && + Math::Similar(vec.y, 21.52, 0.001) && + Math::Similar(vec.z, -60.3646, 0.001), + wss.str().c_str()); + + return; + } + + // Tests if matrix scaling works ( x axis only ) + TEST_METHOD(MatrixMult_Scale_X) + { + // Create vector + Vector3d vec(5, 6, 7); + + // Create scaling matrix + Matrix4x4 mat; + mat[0] = { 3, 0, 0, 0 }; + mat[1] = { 0, 1, 0, 0 }; + mat[2] = { 0, 0, 1, 0 }; + + // Scale vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Vector3d(15, 6, 7) == vec, wss.str().c_str()); + + return; + } + + // Tests if matrix scaling works ( y axis only ) + TEST_METHOD(MatrixMult_Scale_Y) + { + // Create vector + Vector3d vec(5, 6, 7); + + // Create scaling matrix + Matrix4x4 mat; + mat[0] = { 1, 0, 0, 0 }; + mat[1] = { 0, 3, 0, 0 }; + mat[2] = { 0, 0, 1, 0 }; + + // Scale vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Vector3d(5, 18, 7) == vec, wss.str().c_str()); + + return; + } + + // Tests if matrix scaling works ( z axis only ) + TEST_METHOD(MatrixMult_Scale_Z) + { + // Create vector + Vector3d vec(5, 6, 7); + + // Create scaling matrix + Matrix4x4 mat; + mat[0] = { 1, 0, 0, 0 }; + mat[1] = { 0, 1, 0, 0 }; + mat[2] = { 0, 0, 3, 0 }; + + // Scale vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Vector3d(5, 6, 21) == vec, wss.str().c_str()); + + return; + } + + // Tests if matrix scaling works ( all axes ) + TEST_METHOD(MatrixMult_Scale_Combined) + { + // Create vector + Vector3d vec(5, 6, 7); + + // Create scaling matrix + Matrix4x4 mat; + mat[0] = { 4, 0, 0, 0 }; + mat[1] = { 0, 5, 0, 0 }; + mat[2] = { 0, 0, 8, 0 }; + + // Scale vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Vector3d(20, 30, 56) == vec, wss.str().c_str()); + + return; + } + + // Tests if translation via matrix multiplication works + TEST_METHOD(MatrixMult_Translation) + { + // Create vector + Vector3d vec(5, 6, 7); + + // Create scaling matrix + Matrix4x4 mat; + mat[0] = { 1, 0, 0, 155 }; + mat[1] = { 0, 1, 0, -23 }; + mat[2] = { 0, 0, 1, 333 }; + + // Translate vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Vector3d(160, -17, 340) == vec, wss.str().c_str()); + + return; + } + + // Tests the multiplication operator (*) with a simple matrix. All other tests used the * operator (without the '=') + TEST_METHOD(MatrixMult_Not_Using_MultEqualsOperator) + { + // Create vector + Vector3d vec(5.1, 6.4, 7.99); + + // Create scaling and translation matrix + Matrix4x4 mat; + mat[0] = { 3.8, 0, 0, -5.1 }; + mat[1] = { 0, -1.5, 0, 15.2 }; + mat[2] = { 0, 0, 3.01, 19.9 }; + + // Transform vector + vec = vec * mat; + + // Did we succeed? + Vector3d expected( + 5.1 * 3.8 - 5.1, + 6.4 * -1.5 + 15.2, + 7.99 * 3.01 + 19.9 + ); + + std::wstringstream wss; + wss << std::endl; + wss << "Expected: " << expected << std::endl; + wss << "Actual: " << vec; + + Assert::IsTrue(expected == vec, wss.str().c_str()); + + return; + } + + // A simple matrix multiplication tested on an int vector + TEST_METHOD(MatrixMult_Dirty_Int_Check) + { + // Create vector + Vector3i vec(5, 6, 7); + + // Create scaling and translation matrix + Matrix4x4 mat; + mat[0] = { 3, 0, 0, -5 }; + mat[1] = { 0, -1, 0, 15 }; + mat[2] = { 0, 0, 3, 20 }; + + // Transform vector + vec *= mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Vector3i( + 5*3 + -5, + 6*-1 + 15, + 7*3 + 20 + ) == vec, wss.str().c_str()); + + return; + } + + // Tests the multiplication operator (*) with a simple matrix. All other tests used the * operator (without the '=') + TEST_METHOD(MatrixMult_Dirty_Int_Check_Not_Using_MultEqualsOperator) + { + // Create vector + Vector3i vec(5, 6, 7); + + // Create scaling and translation matrix + Matrix4x4 mat; + mat[0] = { 3, 0, 0, -5 }; + mat[1] = { 0, -1, 0, 15 }; + mat[2] = { 0, 0, 3, 20 }; + + // Scale vector + vec = vec * mat; + + // Did we succeed? + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Vector3i( + 5 * 3 + -5, + 6 * -1 + 15, + 7 * 3 + 20 + ) == vec, wss.str().c_str()); + + return; + } + + //This tests the multiplication equals operator (*=) procedurally + TEST_METHOD(MatrixMult_Equals_Procedural) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Generate parameters + double initialX = LARGE_RAND_DOUBLE; + double initialY = LARGE_RAND_DOUBLE; + double initialZ = LARGE_RAND_DOUBLE; + double scaleX = LARGE_RAND_DOUBLE; + double scaleY = LARGE_RAND_DOUBLE; + double scaleZ = LARGE_RAND_DOUBLE; + double transX = LARGE_RAND_DOUBLE; + double transY = LARGE_RAND_DOUBLE; + double transZ = LARGE_RAND_DOUBLE; + + // Create vector + Vector3d vec(initialX, initialY, initialZ); + + // Create matrix + Matrix4x4 mat; + mat[0] = { scaleX, 0, 0, transX }; + mat[1] = { 0, scaleY, 0, transY }; + mat[2] = { 0, 0, scaleZ, transZ }; + mat[3] = { 0, 0, 0, 0 }; + + // Create expected vector + Vector3d expected( + initialX * scaleX + transX, + initialY * scaleY + transY, + initialZ * scaleZ + transZ + ); + + // Transform vector + vec *= mat; + + // Compare + Assert::IsTrue(vec == expected); + } + + return; + } + + //This tests the multiplication operator (*) procedurally + TEST_METHOD(MatrixMult_Procedural) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + // Generate parameters + double initialX = LARGE_RAND_DOUBLE; + double initialY = LARGE_RAND_DOUBLE; + double initialZ = LARGE_RAND_DOUBLE; + double scaleX = LARGE_RAND_DOUBLE; + double scaleY = LARGE_RAND_DOUBLE; + double scaleZ = LARGE_RAND_DOUBLE; + double transX = LARGE_RAND_DOUBLE; + double transY = LARGE_RAND_DOUBLE; + double transZ = LARGE_RAND_DOUBLE; + + // Create vector + Vector3d vec(initialX, initialY, initialZ); + + // Create matrix + Matrix4x4 mat; + mat[0] = { scaleX, 0, 0, transX }; + mat[1] = { 0, scaleY, 0, transY }; + mat[2] = { 0, 0, scaleZ, transZ }; + mat[3] = { 0, 0, 0, 0 }; + + // Create expected vector + Vector3d expected( + initialX * scaleX + transX, + initialY * scaleY + transY, + initialZ * scaleZ + transZ + ); + + // Transform vector + vec = vec * mat; + + // Compare + Assert::IsTrue(vec == expected); + } + + return; + } + + // Tests loose comparison via Vector3d::Similar -> true + TEST_METHOD(Similar_True) + { + Assert::IsTrue( + Vector3d(0.00000000000000000000001, -6.6666666666666666666666666666, 9.9999999999999999999999999999).Similar( + Vector3d(0, -6.666666667, 10) + )); + return; + } + + // Tests loose comparison via Vector3d::Similar -> false + TEST_METHOD(Similar_False) + { + Assert::IsFalse( + Vector3d(0.00000000000000000000001, -6.6666666666666666666666666666, 9.9999999999999999999999999999).Similar( + Vector3d(0.1, -6.7, 10.1) + )); + return; + } + + // Tests that the move constructor works + TEST_METHOD(Move_Constructor) + { + Vector3d a(1,2,3); + Vector3d b(std::move(a)); + + Assert::AreEqual(b.x, 1.0); + Assert::AreEqual(b.y, 2.0); + Assert::AreEqual(b.z, 3.0); + + return; + } + + // Tests that the move operator works + TEST_METHOD(Move_Operator) + { + Vector3d a(1, 2, 3); + Vector3d b = std::move(a); + + Assert::AreEqual(b.x, 1.0); + Assert::AreEqual(b.y, 2.0); + Assert::AreEqual(b.z, 3.0); + + return; + } + }; +} diff --git a/Test/Vector4.cpp b/Test/Vector4.cpp new file mode 100644 index 0000000..ed8b992 --- /dev/null +++ b/Test/Vector4.cpp @@ -0,0 +1,824 @@ +#include "CppUnitTest.h" +#include "../Eule/Vector4.h" +#include "../Eule/Matrix4x4.h" +#include "../Eule/Math.h" +#include "../_TestingUtilities/HandyMacros.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace Vectors +{ + TEST_CLASS(_Vector4) + { + private: + std::mt19937 rng; + + public: + // Constructor + _Vector4() + { + rng = std::mt19937((std::random_device())()); + return; + } + + // Tests if all values are 0 after initialization via default constructor + TEST_METHOD(New_Vector_All_0) + { + Vector4d v4; + + Assert::AreEqual(0.0, v4.x); + Assert::AreEqual(0.0, v4.y); + Assert::AreEqual(0.0, v4.z); + Assert::AreEqual(0.0, v4.w); + + return; + } + + // Tests if values can be set via the constructor + TEST_METHOD(Can_Set_Values_Constructor) + { + Vector4d v4(69, 32, 16, 10); + + Assert::AreEqual(69.0, v4.x); + Assert::AreEqual(32.0, v4.y); + Assert::AreEqual(16.0, v4.z); + Assert::AreEqual(10.0, v4.w); + + return; + } + + // Tests if values can be set via letters + TEST_METHOD(Can_Set_Values_Letters) + { + Vector4d v4; + v4.x = 69; + v4.y = 32; + v4.z = 16; + v4.w = 10; + + Assert::AreEqual(69.0, v4.x); + Assert::AreEqual(32.0, v4.y); + Assert::AreEqual(16.0, v4.z); + Assert::AreEqual(10.0, v4.w); + + return; + } + + // Tests if values can be set via array descriptors + TEST_METHOD(Can_Set_Values_ArrayDescriptor) + { + Vector4d v4; + v4[0] = 69; + v4[1] = 32; + v4[2] = 16; + v4[3] = 10; + + Assert::AreEqual(69.0, v4.x); + Assert::AreEqual(32.0, v4.y); + Assert::AreEqual(16.0, v4.z); + Assert::AreEqual(10.0, v4.w); + + return; + } + + // Tests if values can be set via an initializer list + TEST_METHOD(Can_Set_Values_InitializerList) + { + Vector4d v4 = { 69, 32, 16, 10 }; + + Assert::AreEqual(69.0, v4.x); + Assert::AreEqual(32.0, v4.y); + Assert::AreEqual(16.0, v4.z); + Assert::AreEqual(10.0, v4.w); + + return; + } + + // Tests for vectors copied via the copy constructor to have the same values + TEST_METHOD(Copy_Constructor_Same_Values) + { + Vector4d a(69, 32, 16, 10); + Vector4d b(a); + + Assert::AreEqual(69.0, b.x); + Assert::AreEqual(32.0, b.y); + Assert::AreEqual(16.0, b.z); + Assert::AreEqual(10.0, b.w); + + return; + } + + // Tests for vectors copied via the equals operator to have the same values + TEST_METHOD(Operator_Equals_Same_Values) + { + Vector4d a(69, 32, 16, 10); + Vector4d b = a; + + Assert::AreEqual(69.0, b.x); + Assert::AreEqual(32.0, b.y); + Assert::AreEqual(16.0, b.z); + Assert::AreEqual(10.0, b.w); + + return; + } + + // Tests for vectors copied via the copy constructor to be modifyable without modifying the original object + TEST_METHOD(Copy_Constructor_Independent) + { + Vector4d a(69, 32, 16, 10); + Vector4d b(a); + + b.x = 169; + b.y = 132; + b.z = 116; + b.w = 110; + + Assert::AreEqual(69.0, a.x); + Assert::AreEqual(32.0, a.y); + Assert::AreEqual(16.0, a.z); + Assert::AreEqual(10.0, a.w); + + Assert::AreEqual(169.0, b.x); + Assert::AreEqual(132.0, b.y); + Assert::AreEqual(116.0, b.z); + Assert::AreEqual(110.0, b.w); + + return; + } + + // Tests for vectors copied via the equals operator to be modifyable without modifying the original object + TEST_METHOD(Operator_Equals_Independent) + { + Vector4d a(69, 32, 16, 10); + Vector4d b = a; + + b.x = 169; + b.y = 132; + b.z = 116; + b.w = 110; + + Assert::AreEqual(69.0, a.x); + Assert::AreEqual(32.0, a.y); + Assert::AreEqual(16.0, a.z); + Assert::AreEqual(10.0, a.w); + + Assert::AreEqual(169.0, b.x); + Assert::AreEqual(132.0, b.y); + Assert::AreEqual(116.0, b.z); + Assert::AreEqual(110.0, b.w); + + return; + } + + // Tests the SqrMagnitude method to work as expected with random numbers + TEST_METHOD(SqrMagnitude) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double w = LARGE_RAND_DOUBLE; + double expected = x*x + y*y + z*z + w*w; + + Assert::AreEqual(expected, Vector4d(x, y, z, w).SqrMagnitude()); + } + + return; + } + + // Tests for the length of the vector (0,0,0,0) being 0 + TEST_METHOD(Magnitude_Is_0_On_Vec0) + { + Assert::AreEqual(0.0, Vector4d(0, 0, 0, 0).Magnitude()); + return; + } + + // Tests for a vector of a known length to actually return that + TEST_METHOD(Magnitude_One_Axis_X) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + Vector4d vec(x, 0, 0, 0); + Assert::AreEqual(abs(x), vec.Magnitude()); + } + + return; + } + + // Tests for a vector of a known length to actually return that + TEST_METHOD(Magnitude_One_Axis_Y) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double y = LARGE_RAND_DOUBLE; + Vector4d vec(0, y, 0, 0); + Assert::AreEqual(abs(y), vec.Magnitude()); + } + + return; + } + + // Tests for a vector of a known length to actually return that + TEST_METHOD(Magnitude_One_Axis_Z) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double z = LARGE_RAND_DOUBLE; + Vector4d vec(0, 0, z, 0); + Assert::AreEqual(abs(z), vec.Magnitude()); + } + + return; + } + + // Tests for a vector of a known length to actually return that + TEST_METHOD(Magnitude_One_Axis_W) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double w = LARGE_RAND_DOUBLE; + Vector4d vec(0, 0, 0, w); + Assert::AreEqual(abs(w), vec.Magnitude()); + } + + return; + } + + // Tests for a known result + TEST_METHOD(Magnitude) + { + // Ya'll got more of 'dem digits? + Assert::AreEqual(78.5746530377322045524124405346810817718505859375, Vector4d(-23.76, 15.82, 66.75, 30.06).Magnitude()); + return; + } + + // Tests for expected lerp result 0.00 + TEST_METHOD(Lerp_000) + { + Vector4d a(100, 1000, 10, -200); + Vector4d b(200, 4000, 100, 200); + Vector4d res = a.Lerp(b, 0.00); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(a == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 0.25 + TEST_METHOD(Lerp_025) + { + Vector4d a(100, 1000, 10, -200); + Vector4d b(200, 4000, 100, 200); + Vector4d res = a.Lerp(b, 0.25); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(Vector4d(125, 1750, 32.5, -100) == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 0.50 + TEST_METHOD(Lerp_050) + { + Vector4d a(100, 1000, 10, -200); + Vector4d b(200, 4000, 100, 200); + Vector4d res = a.Lerp(b, 0.50); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(Vector4d(150, 2500, 55, 0) == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 0.75 + TEST_METHOD(Lerp_075) + { + Vector4d a(100, 1000, 10, -200); + Vector4d b(200, 4000, 100, 200); + Vector4d res = a.Lerp(b, 0.75); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(Vector4d(175, 3250, 77.5, 100) == res, wss.str().c_str()); + return; + } + + // Tests for expected lerp result 1.00 + TEST_METHOD(Lerp_100) + { + Vector4d a(100, 1000, 10, -200); + Vector4d b(200, 4000, 100, 200); + Vector4d res = a.Lerp(b, 1.00); + + std::wstringstream wss; + wss << res; + Assert::IsTrue(b == res, wss.str().c_str()); + return; + } + + // Tests lerpself + TEST_METHOD(LerpSelf) + { + Vector4d a(100, 1000, 10, -200); + Vector4d b(200, 4000, 100, 200); + + a.LerpSelf(b, 0.75); + + std::wstringstream wss; + wss << a; + Assert::IsTrue(Vector4d(175, 3250, 77.5, 100) == a, wss.str().c_str()); + return; + } + + // Tests if an input vector of length 0 is handled correctly by the normalize method + TEST_METHOD(Normalize_Length_Before_Is_0) + { + Vector4d vec(0, 0, 0, 0); + vec.NormalizeSelf(); + Assert::AreEqual(0.0, vec.Magnitude()); + return; + } + + // Tests for any normalized vector to be of length 1 + TEST_METHOD(Normalize_Length_Is_1) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double w = LARGE_RAND_DOUBLE; + + Vector4d vec(x, y, z, w); + + // Prevent a vector of length 0 going in + if (vec.SqrMagnitude() == 0) + vec.x++; + + std::wstringstream wss; + wss << vec; + Assert::IsTrue(Math::Similar(vec.Normalize().Magnitude(), 1.0), wss.str().c_str()); // Account for floating point inaccuracy + } + + return; + } + + // Tests the normalize method with known values + TEST_METHOD(Normalize_Oracle) + { + // Setup + Vector4d v(3.2, -5.3, 9.88, 69.420); + + // Exercise + v.NormalizeSelf(); + + // Verify + Vector4d expected(0.0454594951, -0.07529228877, 0.14035619114, 0.98618692201); + Assert::IsTrue(v.Similar(expected)); + } + + // Kinda dumb method, but ok lol + // DON'T NORMALIZE INT-VECTORS WHAT IS WRONG WITH YOU + TEST_METHOD(Normalized_Int_Vector_Is_0) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + int x = LARGE_RAND_INT; + int y = LARGE_RAND_INT; + int z = LARGE_RAND_INT; + int w = LARGE_RAND_INT; + + Vector4i vec(x, y, z, w); + + vec.NormalizeSelf(); + + std::wstringstream wss; + wss << vec; + Assert::AreEqual(0.0, vec.Magnitude(), wss.str().c_str()); + } + } + + // Tests that NormalizeSelf() results in the same as Normalize() + TEST_METHOD(NormalizeSelf_IsSameAs_Normalize) + { + // Run test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + Vector4d vec(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE); + + Vector4d nVec = vec.Normalize(); + vec.NormalizeSelf(); + + Assert::IsTrue(nVec == vec); + } + + return; + } + + // Tests for the VectorScale() method to work + TEST_METHOD(VectorScale) + { + // Run test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + const double ax = LARGE_RAND_DOUBLE; + const double ay = LARGE_RAND_DOUBLE; + const double az = LARGE_RAND_DOUBLE; + const double aw = LARGE_RAND_DOUBLE; + const double bx = LARGE_RAND_DOUBLE; + const double by = LARGE_RAND_DOUBLE; + const double bz = LARGE_RAND_DOUBLE; + const double bw = LARGE_RAND_DOUBLE; + + Vector4d a(ax, ay, az, aw); + Vector4d b(bx, by, bz, bw); + + Vector4d target( + ax * bx, + ay * by, + az * bz, + aw * bw + ); + + Assert::IsTrue(a.VectorScale(b) == target); + } + + return; + } + + // Tests for operator- (unary) to work + TEST_METHOD(Operator_Unary_Negative) + { + Vector4d v(29, -5, 35, -69); + + Assert::IsTrue(Vector4d(-29, 5, -35, 69) == -v); + + return; + } + + // Tests for operator+ to work as expected + TEST_METHOD(Operator_Add) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double aw = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + double bw = LARGE_RAND_DOUBLE; + + Vector4d a(ax, ay, az, aw); + Vector4d b(bx, by, bz, bw); + + Assert::IsTrue(Vector4d(ax + bx, ay + by, az + bz, aw + bw) == a + b); + } + + return; + } + + // Tests for operator+= to work as expected + TEST_METHOD(Operator_Add_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double aw = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + double bw = LARGE_RAND_DOUBLE; + + Vector4d a(ax, ay, az, aw); + a += Vector4d(bx, by, bz, bw); + + Assert::IsTrue(Vector4d(ax + bx, ay + by, az + bz, aw + bw) == a); + } + + return; + } + + // Tests for operator- to work as expected + TEST_METHOD(Operator_Sub) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double aw = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + double bw = LARGE_RAND_DOUBLE; + + Vector4d a(ax, ay, az, aw); + Vector4d b(bx, by, bz, bw); + + Assert::IsTrue(Vector4d(ax - bx, ay - by, az - bz, aw - bw) == a - b); + } + + return; + } + + // Tests for operator-= to work as expected + TEST_METHOD(Operator_Sub_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double aw = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + double bw = LARGE_RAND_DOUBLE; + + Vector4d a(ax, ay, az, aw); + a -= Vector4d(bx, by, bz, bw); + + Assert::IsTrue(Vector4d(ax - bx, ay - by, az - bz, aw - bw) == a); + } + + return; + } + + // Tests for operator* to work as expected + TEST_METHOD(Operator_Mult) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double w = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector4d a(x, y, z, w); + + Assert::IsTrue(Vector4d(x * scalar, y * scalar, z * scalar, w * scalar) == a * scalar); + } + + return; + } + + // Tests for operator*= to work as expected + TEST_METHOD(Operator_Mult_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double w = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector4d a(x, y, z, w); + a *= scalar; + + Assert::IsTrue(Vector4d(x * scalar, y * scalar, z * scalar, w * scalar) == a); + } + + return; + } + + // Tests for operator/ to work as expected + TEST_METHOD(Operator_Div) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double w = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector4d a(x, y, z, w); + + Assert::IsTrue(Vector4d(x / scalar, y / scalar, z / scalar, w / scalar) == a / scalar); + } + + return; + } + + // Tests for operator/= to work as expected + TEST_METHOD(Operator_Div_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double x = LARGE_RAND_DOUBLE; + double y = LARGE_RAND_DOUBLE; + double z = LARGE_RAND_DOUBLE; + double w = LARGE_RAND_DOUBLE; + double scalar = LARGE_RAND_DOUBLE; + + Vector4d a(x, y, z, w); + a /= scalar; + + Assert::IsTrue(Vector4d(x / scalar, y / scalar, z / scalar, w / scalar) == a); + } + + return; + } + + // Tests for operator== to work as expected + TEST_METHOD(Operator_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double aw = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + double bw = LARGE_RAND_DOUBLE; + + Vector4d a(ax, ay, az, aw); + Vector4d b(bx, by, bz, bw); + + Assert::IsTrue( + ((ax == bx) && (ay == by) && (az == bz) && (aw == bw)) == + (a == b) + ); + } + + return; + } + + // Tests for operator!= to work as expected + TEST_METHOD(Operator_Not_Equals) + { + // Test 1000 times + for (std::size_t i = 0; i < 1000; i++) + { + double ax = LARGE_RAND_DOUBLE; + double ay = LARGE_RAND_DOUBLE; + double az = LARGE_RAND_DOUBLE; + double aw = LARGE_RAND_DOUBLE; + double bx = LARGE_RAND_DOUBLE; + double by = LARGE_RAND_DOUBLE; + double bz = LARGE_RAND_DOUBLE; + double bw = LARGE_RAND_DOUBLE; + + Vector4d a(ax, ay, az, aw); + Vector4d b(bx, by, bz, bw); + + Assert::IsTrue( + ((ax != bx) || (ay != by) || (az != bz) || (aw != bw)) == + (a != b) + ); + } + + return; + } + + // Tests matrix multiplication with the multiplication operator (*) with a known result + TEST_METHOD(MatrixMult) + { + Vector4d vec(117, 12, -36, 500); + + Matrix4x4 mat; + mat[0] = { -43.7, 83, 96, 86 }; + mat[1] = { 12, 34.3, 43, -47 }; + mat[2] = { 36, 67, 48.9, -32 }; + mat[3] = { -69, 47, 21, 89.01 }; + + vec = vec * mat; + + Assert::IsTrue(Vector4d(35427.1, -23232.4, -12744.4, 36240) == vec); + + return; + } + + // Tests matrix multiplication with the multiplication equals operator (*=) with a known result + TEST_METHOD(MatrixMult_Equals) + { + Vector4d vec(117, 12, -36, 500); + + Matrix4x4 mat; + mat[0] = { -43.7, 83, 96, 86 }; + mat[1] = { 12, 34.3, 43, -47 }; + mat[2] = { 36, 67, 48.9, -32 }; + mat[3] = { -69, 47, 21, 89.01 }; + + vec *= mat; + + Assert::IsTrue(Vector4d(35427.1, -23232.4, -12744.4, 36240) == vec); + + return; + } + + // Tests matrix multiplication with the multiplication operator (*) with a known result, but with an int-vector + TEST_METHOD(MatrixMult_Int) + { + Vector4i vec(112, -420, 80085, 1); + + Matrix4x4 mat; + mat[0] = { 12, 83, 96, 86 }; + mat[1] = { 12, -57, 43, -47 }; + mat[2] = { 36, 67, 61, -32 }; + mat[3] = { -69, 47, 21, 99 }; + + vec = vec * mat; + + Assert::IsTrue(Vector4i(7654730, 3468892, 4861045, 1654416) == vec); + + return; + } + + // Tests matrix multiplication with the multiplication equals operator (*=) with a known result, but with an int - vector + TEST_METHOD(MatrixMult_Equals_Int) + { + Vector4i vec(112, -420, 80085, 1); + + Matrix4x4 mat; + mat[0] = { 12, 83, 96, 86 }; + mat[1] = { 12, -57, 43, -47 }; + mat[2] = { 36, 67, 61, -32 }; + mat[3] = { -69, 47, 21, 99 }; + + vec *= mat; + + Assert::IsTrue(Vector4i(7654730, 3468892, 4861045, 1654416) == vec); + + return; + } + + // Tests loose comparison via Vector4d::Similar -> true + TEST_METHOD(Loose_Comparison_True_Vector4d) + { + Assert::IsTrue( + Vector4d(0.00000000000000000000001, -6.6666666666666666666666666666, 9.9999999999999999999999999999, -3.3333333333333333333333333333333333333).Similar( + Vector4d(0, -6.666666667, 10, -3.33333333333333) + )); + return; + } + + // Tests loose comparison via Vector4d::Similar -> false + TEST_METHOD(Loose_Comparison_False_Vector4d) + { + Assert::IsFalse( + Vector4d(0.00000000000000000000001, -6.6666666666666666666666666666, 9.9999999999999999999999999999, -3.3333333333333333333333333333333333333).Similar( + Vector4d(0.1, -6.7, 10.1, -3.333) + )); + return; + } + + // Tests that the move constructor works + TEST_METHOD(Move_Constructor) + { + Vector4d a(1, 2, 3, 4); + Vector4d b(std::move(a)); + + Assert::AreEqual(b.x, 1.0); + Assert::AreEqual(b.y, 2.0); + Assert::AreEqual(b.z, 3.0); + Assert::AreEqual(b.w, 4.0); + + return; + } + + // Tests that the move operator works + TEST_METHOD(Move_Operator) + { + Vector4d a(1, 2, 3, 4); + Vector4d b = std::move(a); + + Assert::AreEqual(b.x, 1.0); + Assert::AreEqual(b.y, 2.0); + Assert::AreEqual(b.z, 3.0); + Assert::AreEqual(b.w, 4.0); + + return; + } + }; +} diff --git a/Test/VectorConversion.cpp b/Test/VectorConversion.cpp new file mode 100644 index 0000000..27c6517 --- /dev/null +++ b/Test/VectorConversion.cpp @@ -0,0 +1,220 @@ +#include "CppUnitTest.h" +#include "../Eule/Vector2.h" +#include "../Eule/Vector3.h" +#include "../Eule/Vector4.h" +#include "../_TestingUtilities/HandyMacros.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Eule; + +namespace Vectors +{ + TEST_CLASS(_VectorConversion) + { + private: + std::mt19937 rng; + + public: + // Constructor + _VectorConversion() + { + rng = std::mt19937((std::random_device())()); + return; + + } + + // Tests that conversion vector2 -> vector3 works + TEST_METHOD(Convert_Vector2_To_Vector3) + { + // Run test 100 times + for (std::size_t i = 0; i < 100; i++) + { + Vector2d v2(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE); + Vector3d v3 = v2; + + Assert::AreEqual(v2.x, v3.x); + Assert::AreEqual(v2.y, v3.y); + Assert::AreEqual(0.0, v3.z); + } + + return; + } + + // Tests that conversion vector2 -> vector4 works + TEST_METHOD(Convert_Vector2_To_Vector4) + { + // Run test 100 times + for (std::size_t i = 0; i < 100; i++) + { + Vector2d v2(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE); + Vector4d v4 = v2; + + Assert::AreEqual(v2.x, v4.x); + Assert::AreEqual(v2.y, v4.y); + Assert::AreEqual(0.0, v4.z); + Assert::AreEqual(0.0, v4.w); + } + + return; + } + + // Tests that conversion vector3 -> vector2 works + TEST_METHOD(Convert_Vector3_To_Vector2) + { + // Run test 100 times + for (std::size_t i = 0; i < 100; i++) + { + Vector3d v3(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE); + Vector2d v2 = v3; + + Assert::AreEqual(v3.x, v2.x); + Assert::AreEqual(v3.y, v2.y); + } + + return; + } + + // Tests that conversion vector3 -> vector4 works + TEST_METHOD(Convert_Vector3_To_Vector4) + { + // Run test 100 times + for (std::size_t i = 0; i < 100; i++) + { + Vector3d v3(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE); + Vector4d v4 = v3; + + Assert::AreEqual(v3.x, v4.x); + Assert::AreEqual(v3.y, v4.y); + Assert::AreEqual(v3.z, v4.z); + Assert::AreEqual(0.0, v4.w); + } + + return; + } + + // Tests that conversion vector4 -> vector42 works + TEST_METHOD(Convert_Vector4_To_Vector2) + { + // Run tests 100 times + for (std::size_t i = 0; i < 100; i++) + { + Vector4d v4(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE); + Vector2d v2 = v4; + + Assert::AreEqual(v4.x, v2.x); + Assert::AreEqual(v4.y, v2.y); + } + + return; + } + + // Tests that conversion vector4 -> vector3 works + TEST_METHOD(Convert_Vector4_To_Vector3) + { + // Run tests 100 times + for (std::size_t i = 0; i < 100; i++) + { + Vector4d v4(LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE, LARGE_RAND_DOUBLE); + Vector3d v3 = v4; + + Assert::AreEqual(v4.x, v3.x); + Assert::AreEqual(v4.y, v3.y); + Assert::AreEqual(v4.z, v3.z); + } + + return; + } + + // Tests Vector2i -> Vector2d + TEST_METHOD(Convert_Vector2i_To_Vector2d) + { + // Setup + Vector2i vi(69, 70); + + // Exercise + Vector2d vd = vi.ToDouble(); + + // Verify + Assert::IsTrue(Vector2d(69, 70) == vd); + + return; + } + + // Tests Vector2d -> Vector2i + TEST_METHOD(Convert_Vector2d_To_Vector2i) + { + // Setup + Vector2d vd(69.2, 70.8); + + // Exercise + Vector2i vi = vd.ToInt(); + + // Verify + Assert::IsTrue(Vector2i(69, 70) == vi); + + return; + } + + // Tests Vector3i -> Vector3d + TEST_METHOD(Convert_Vector3i_To_Vector3d) + { + // Setup + Vector3i vi(69, 70, 122); + + // Exercise + Vector3d vd = vi.ToDouble(); + + // Verify + Assert::IsTrue(Vector3d(69, 70, 122) == vd); + + return; + } + + // Tests Vector3d -> Vector3i + TEST_METHOD(Convert_Vector3d_To_Vector3i) + { + // Setup + Vector3d vd(69.2, 70.8, 122); + + // Exercise + Vector3i vi = vd.ToInt(); + + // Verify + Assert::IsTrue(Vector3i(69, 70, 122) == vi); + + return; + } + + // Tests Vector4i -> Vector4d + TEST_METHOD(Convert_Vector4i_To_Vector4d) + { + // Setup + Vector4i vi(69, 70, 122, 199); + + // Exercise + Vector4d vd = vi.ToDouble(); + + // Verify + Assert::IsTrue(Vector4d(69, 70, 122, 199) == vd); + + return; + } + + // Tests Vector4d -> Vector4i + TEST_METHOD(Convert_Vector4d_To_Vector4i) + { + // Setup + Vector4d vd(69.2, 70.8, 122, 199.501); + + // Exercise + Vector4i vi = vd.ToInt(); + + // Verify + Assert::IsTrue(Vector4i(69, 70, 122, 199) == vi); + + return; + } + }; +} diff --git a/Test/_Test_Eule.vcxproj b/Test/_Test_Eule.vcxproj new file mode 100644 index 0000000..120fed0 --- /dev/null +++ b/Test/_Test_Eule.vcxproj @@ -0,0 +1,186 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + {e15cd460-78cb-4b3f-be85-c1e3205247b1} + + + + + + + + + + + + + + + + + + + + + + + + 16.0 + {2B6C03E2-179C-45EA-9FDE-45D0214B4D5E} + Win32Proj + TestEule + 10.0 + NativeUnitTestProject + + + + DynamicLibrary + true + v142 + Unicode + false + + + DynamicLibrary + false + v142 + true + Unicode + false + + + DynamicLibrary + true + v142 + Unicode + false + + + DynamicLibrary + false + v142 + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + false + + + true + + + true + + + false + + + + NotUsing + Level3 + true + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + _DEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + + \ No newline at end of file diff --git a/Test/_Test_Eule.vcxproj.filters b/Test/_Test_Eule.vcxproj.filters new file mode 100644 index 0000000..b966449 --- /dev/null +++ b/Test/_Test_Eule.vcxproj.filters @@ -0,0 +1,79 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {b315014b-6f13-4ae9-bb64-68cd50dde287} + + + {4af509be-a959-45e0-bec2-03894a16ad60} + + + + + Tests\Math + + + Tests\Math + + + Tests\Math + + + Tests\Math + + + Tests\Math + + + Tests\Math + + + Tests\Math + + + Tests\Math + + + Tests\Math + + + Tests\Math + + + Tests\Math + + + Tests + + + Tests + + + Tests + + + Tests + + + Tests + + + Tests + + + Tests + + + \ No newline at end of file diff --git a/_TestingUtilities/HandyMacros.h b/_TestingUtilities/HandyMacros.h new file mode 100644 index 0000000..f584d7f --- /dev/null +++ b/_TestingUtilities/HandyMacros.h @@ -0,0 +1,5 @@ +#pragma once +#define LARGE_RAND_DOUBLE (((rng() % 6969000) - 3500000) / 1000.0) +#define LARGE_RAND_POSITIVE_DOUBLE (((rng() % 3500000)) / 1000.0) +#define LARGE_RAND_INT ((rng() % 6969) - 3500) +#define LARGE_RAND_POSITIVE_INT ((rng() % 3500)) diff --git a/_TestingUtilities/MemoryLeakDetector.h b/_TestingUtilities/MemoryLeakDetector.h new file mode 100644 index 0000000..a389833 --- /dev/null +++ b/_TestingUtilities/MemoryLeakDetector.h @@ -0,0 +1,44 @@ +#pragma once +#include + +// Don't even allow compilation in release mode unless handled correctly. +// This class ONLY WORKS IN DEBUG MODE!!! +#ifdef _DEBUG +class MemoryLeakDetector +{ +public: + // Defines the initial mem state to check against + void Init() + { + _CrtMemCheckpoint(&stateInit); + return; + }; + + // Returns whether or not the process uses more memory than at the point when Init() was called + bool DetectLeak() + { + _CrtMemCheckpoint(&stateEnd); + return Evaluate(); + }; + + // Returns the absolute memory difference since calling Init(). WARNING: The ABSOLUTE difference! No negative values! + int Difference() + { + // I know that it's the exact same code as in DetectedLeak(). + _CrtMemCheckpoint(&stateEnd); + return Evaluate(); + } + + +private: + int Evaluate() + { + return _CrtMemDifference(&stateBfr, &stateInit, &stateEnd); + } + + _CrtMemState stateInit; + _CrtMemState stateEnd; + _CrtMemState stateBfr; + +}; +#endif diff --git a/_TestingUtilities/Testutil.h b/_TestingUtilities/Testutil.h new file mode 100644 index 0000000..f0a0d4c --- /dev/null +++ b/_TestingUtilities/Testutil.h @@ -0,0 +1,27 @@ +#pragma once +#include + +class Testutil +{ +public: + template + static double Stddev(const std::vector& distribution) + { + // Calculate mean + double sum = 0; + for (const T& i : distribution) + sum += i; + const double mean = sum / distribution.size(); + + // Calculate variance + sum = 0; + for (const T& i : distribution) + sum += (i - mean) * (i - mean); + const double variance = sum / (distribution.size() - 1); + + // Calcuate stddev + const double stddev = sqrt(variance); + + return stddev; + } +}; diff --git a/_TestingUtilities/_MemoryLeakDetector.cpp b/_TestingUtilities/_MemoryLeakDetector.cpp new file mode 100644 index 0000000..1f82ecd --- /dev/null +++ b/_TestingUtilities/_MemoryLeakDetector.cpp @@ -0,0 +1,105 @@ +#include "CppUnitTest.h" +#include "MemoryLeakDetector.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace TestingUtilities +{ + TEST_CLASS(_MemoryLeakDetector) + { + public: + // =========== MEMORY LEAK TESTS =========== + // These tests depends on debug-mode for memory insights. + // Thus, they only works in debug mode. + #ifdef _DEBUG + + // Tests to detect no memory leak, if the test does nothing at all + TEST_METHOD(No_Memleak_For_Nothing) + { + MemoryLeakDetector mld; + mld.Init(); + + { + // Do nothing here + } + + Assert::IsFalse(mld.DetectLeak()); + return; + } + + // Tests to detect no memory leak when not even touching pointers + TEST_METHOD(No_Memleak_For_No_Pointer_Action) + { + MemoryLeakDetector mld; + mld.Init(); + + { + int i = 33; + int c = i * 9; + } + + Assert::IsFalse(mld.DetectLeak()); + return; + } + + // Tests to detect no memory leak when correctly cleaning up pointers + TEST_METHOD(No_Memleak_For_Cleaned_Up_Pointers) + { + MemoryLeakDetector mld; + mld.Init(); + + { + int* ptr = new int[333]; + delete[] ptr; + } + + Assert::IsFalse(mld.DetectLeak()); + return; + } + + + // Tests to detect a memory leak when not cleaning up pointers + TEST_METHOD(Memleak_For_No_Pointer_Cleanup) + { + MemoryLeakDetector mld; + mld.Init(); + + { + int* ptr = new int[333]; + } + + Assert::IsTrue(mld.DetectLeak()); + return; + } + + // Tests to detect no memory leak when correctly cleaning up pointers, using C-Methods + TEST_METHOD(No_Memleak_For_Cleaned_Up_Pointers_C_Like) + { + MemoryLeakDetector mld; + mld.Init(); + + { + int* ptr = (int*)malloc(sizeof(int) * 333); + free(ptr); + } + + Assert::IsFalse(mld.DetectLeak()); + return; + } + + // Tests to detect a memory leak when not cleaning up pointers, using C-Methods + TEST_METHOD(Memleak_For_No_Pointer_Cleanup_C_Like) + { + MemoryLeakDetector mld; + mld.Init(); + + { + int* ptr = (int*)malloc(sizeof(int) * 333); + } + + Assert::IsTrue(mld.DetectLeak()); + return; + } + #endif + }; +} diff --git a/_TestingUtilities/_TestingUtilities.vcxproj b/_TestingUtilities/_TestingUtilities.vcxproj new file mode 100644 index 0000000..3b0d2ca --- /dev/null +++ b/_TestingUtilities/_TestingUtilities.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {7C7C6FE1-FF52-4C65-8FC1-05A8A9D9AEF7} + Win32Proj + TestingUtilities + 10.0 + NativeUnitTestProject + + + + StaticLibrary + true + v142 + Unicode + false + + + StaticLibrary + false + v142 + true + Unicode + false + + + StaticLibrary + true + v142 + Unicode + false + + + StaticLibrary + false + v142 + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + NotUsing + Level3 + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + _DEBUG;_DEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + _DEBUG;WIN32;_DEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + \ No newline at end of file diff --git a/_TestingUtilities/_TestingUtilities.vcxproj.filters b/_TestingUtilities/_TestingUtilities.vcxproj.filters new file mode 100644 index 0000000..6267c32 --- /dev/null +++ b/_TestingUtilities/_TestingUtilities.vcxproj.filters @@ -0,0 +1,29 @@ + + + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {9a838f9d-c9d6-420b-bf8a-df0f91bb4731} + + + + + Headerdateien + + + Headerdateien + + + + + Tests + + + \ No newline at end of file