diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 7dd7973..c28f9fe 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -148,11 +148,11 @@ public: std::vector{ new EffectParameter("3D Perspective", "Controls the strength of the 3D perspective projection.", "perspectiveStrength", VERSION_HINT, 1.0, 0.0, 1.0), new EffectParameter("Focal Length", "Controls the focal length of the 3D perspective effect. A higher focal length makes the image look more flat, and a lower focal length makes the image look more 3D.", "perspectiveFocalLength", VERSION_HINT, 1.0, 0.0, 10.0), - new EffectParameter("Distance (z)", "Controls how far away the 3D object is drawn away from the camera (the Z position).", "perspectiveZPos", VERSION_HINT, 0.1, 0.0, 1.0), + new EffectParameter("Distance (z)", "Controls how far away the 3D object is drawn away from the camera (the Z position).", "perspectiveZPos", VERSION_HINT, 0.0, -10.0, 10.0), new EffectParameter("Rotate Speed", "Controls how fast the 3D object rotates in the direction determined by the rotation sliders below.", "perspectiveRotateSpeed", VERSION_HINT, 0.0, -1.0, 1.0), new EffectParameter("Rotate X", "Controls the rotation of the object in the X axis.", "perspectiveRotateX", VERSION_HINT, 1.0, -1.0, 1.0), new EffectParameter("Rotate Y", "Controls the rotation of the object in the Y axis.", "perspectiveRotateY", VERSION_HINT, 1.0, -1.0, 1.0), - new EffectParameter("Rotate Z", "Controls the rotation of the object in the Z axis.", "perspectiveRotateZ", VERSION_HINT, 0.0, -1.0, 1.0), + new EffectParameter("Rotate Z", "Controls the rotation of the object in the Z axis.", "perspectiveRotateZ", VERSION_HINT, 1.0, -1.0, 1.0), } ); diff --git a/Source/TestMain.cpp b/Source/TestMain.cpp index bc3ae81..6255f26 100644 --- a/Source/TestMain.cpp +++ b/Source/TestMain.cpp @@ -1,6 +1,6 @@ #include -#include "shape/Point.h" -#include "obj/Frustum.h" +#include "obj/Camera.h" +#include "mathter/Common/Approx.hpp" class FrustumTest : public juce::UnitTest { public: @@ -8,100 +8,108 @@ public: void runTest() override { double focalLength = 1; - double ratio = 1; - double nearDistance = 0.1; - double farDistance = 100; - Frustum frustum(focalLength, ratio, nearDistance, farDistance); - frustum.setCameraOrigin(Point(0, 0, -focalLength)); + Camera camera; + camera.setFocalLength(focalLength); + Vec3 position = Vec3(0, 0, -focalLength); + camera.setPosition(position); + Frustum frustum = camera.getFrustum(); beginTest("Focal Plane Frustum In-Bounds"); // Focal plane is at z = 0 - Point points[][2] = { - {Point(0, 0, 0), Point(0, 0, 0)}, - {Point(1, 1, 0), Point(1, 1, 0)}, - {Point(-1, -1, 0), Point(-1, -1, 0)}, - {Point(1, -1, 0), Point(1, -1, 0)}, - {Point(-1, 1, 0), Point(-1, 1, 0)}, - {Point(0.5, 0.5, 0), Point(0.5, 0.5, 0)}, + Vec3 vecs[] = { + Vec3(0, 0, 0), Vec3(0, 0, 0), + Vec3(1, 1, 0), Vec3(1, 1, 0), + Vec3(-1, -1, 0), Vec3(-1, -1, 0), + Vec3(1, -1, 0), Vec3(1, -1, 0), + Vec3(-1, 1, 0), Vec3(-1, 1, 0), + Vec3(0.5, 0.5, 0), Vec3(0.5, 0.5, 0), }; - testFrustumClippedEqualsExpected(points, frustum, 6); + testFrustumClippedEqualsExpected(vecs, camera, frustum, 6); beginTest("Focal Plane Frustum Out-Of-Bounds"); // Focal plane is at z = 0 - Point points2[][2] = { - {Point(1.1, 1.1, 0), Point(1, 1, 0)}, - {Point(-1.1, -1.1, 0), Point(-1, -1, 0)}, - {Point(1.1, -1.1, 0), Point(1, -1, 0)}, - {Point(-1.1, 1.1, 0), Point(-1, 1, 0)}, - {Point(1.1, 0.5, 0), Point(1, 0.5, 0)}, - {Point(-1.1, 0.5, 0), Point(-1, 0.5, 0)}, - {Point(0.5, -1.1, 0), Point(0.5, -1, 0)}, - {Point(0.5, 1.1, 0), Point(0.5, 1, 0)}, - {Point(10, 10, 0), Point(1, 1, 0)}, - {Point(-10, -10, 0), Point(-1, -1, 0)}, - {Point(10, -10, 0), Point(1, -1, 0)}, - {Point(-10, 10, 0), Point(-1, 1, 0)}, + Vec3 vecs2[] = { + Vec3(1.1, 1.1, 0), Vec3(1, 1, 0), + Vec3(-1.1, -1.1, 0), Vec3(-1, -1, 0), + Vec3(1.1, -1.1, 0), Vec3(1, -1, 0), + Vec3(-1.1, 1.1, 0), Vec3(-1, 1, 0), + Vec3(1.1, 0.5, 0), Vec3(1, 0.5, 0), + Vec3(-1.1, 0.5, 0), Vec3(-1, 0.5, 0), + Vec3(0.5, -1.1, 0), Vec3(0.5, -1, 0), + Vec3(0.5, 1.1, 0), Vec3(0.5, 1, 0), + Vec3(10, 10, 0), Vec3(1, 1, 0), + Vec3(-10, -10, 0), Vec3(-1, -1, 0), + Vec3(10, -10, 0), Vec3(1, -1, 0), + Vec3(-10, 10, 0), Vec3(-1, 1, 0), }; - testFrustumClippedEqualsExpected(points2, frustum, 12); + testFrustumClippedEqualsExpected(vecs2, camera, frustum, 12); beginTest("Behind Camera Out-Of-Bounds"); - double minZWorldCoords = -focalLength + nearDistance; + double minZWorldCoords = -focalLength + frustum.nearDistance; - Point points3[][2] = { - {Point(0, 0, -focalLength), Point(0, 0, minZWorldCoords)}, - {Point(0, 0, -100), Point(0, 0, minZWorldCoords)}, - {Point(0.5, 0.5, -focalLength), Point(0.1, 0.1, minZWorldCoords)}, - {Point(10, -10, -focalLength), Point(0.1, -0.1, minZWorldCoords)}, - {Point(-0.5, 0.5, -100), Point(-0.1, 0.1, minZWorldCoords)}, - {Point(-10, 10, -100), Point(-0.1, 0.1, minZWorldCoords)}, + Vec3 vecs3[] = { + Vec3(0, 0, -focalLength), Vec3(0, 0, minZWorldCoords), + Vec3(0, 0, -100), Vec3(0, 0, minZWorldCoords), + Vec3(0.5, 0.5, -focalLength), Vec3(0.1, 0.1, minZWorldCoords), + Vec3(10, -10, -focalLength), Vec3(0.1, -0.1, minZWorldCoords), + Vec3(-0.5, 0.5, -100), Vec3(-0.1, 0.1, minZWorldCoords), + Vec3(-10, 10, -100), Vec3(-0.1, 0.1, minZWorldCoords), }; - testFrustumClippedEqualsExpected(points3, frustum, 6); + testFrustumClippedEqualsExpected(vecs3, camera, frustum, 6); beginTest("3D Point Out-Of-Bounds"); - Point points4[] = { - Point(1, 1, -0.1), - Point(-1, -1, -0.1), - Point(1, -1, -0.1), - Point(-1, 1, -0.1), - Point(0.5, 0.5, minZWorldCoords), + Vec3 vecs4[] = { + Vec3(1, 1, -0.1), + Vec3(-1, -1, -0.1), + Vec3(1, -1, -0.1), + Vec3(-1, 1, -0.1), + Vec3(0.5, 0.5, minZWorldCoords), }; - testFrustumClipOccurs(points4, frustum, 5); + testFrustumClipOccurs(vecs4, camera, frustum, 5); } - Point project(Point& p, double focalLength) { - return Point( + Vec3 project(Vec3& p, double focalLength) { + return Vec3( p.x * focalLength / p.z, p.y * focalLength / p.z, 0 ); } - juce::String errorMessage(Point& actual, Point& expected) { - return "Expected: " + expected.toString() + " Got: " + actual.toString(); + juce::String vec3ToString(Vec3& p) { + return "(" + juce::String(p.x) + ", " + juce::String(p.y) + ", " + juce::String(p.z) + ")"; } - void testFrustumClippedEqualsExpected(Point points[][2], Frustum& frustum, int length) { + juce::String errorMessage(Vec3& actual, Vec3& expected) { + return "Expected: " + vec3ToString(expected) + ", Actual: " + vec3ToString(actual); + } + + void testFrustumClippedEqualsExpected(Vec3 vecs[], Camera& camera, Frustum& frustum, int length) { for (int i = 0; i < length; i++) { - Point p = points[i][0]; + Vec3 p = vecs[2 * i]; + p = camera.toCameraSpace(p); frustum.clipToFrustum(p); - expect(p == points[i][1], errorMessage(p, points[i][1])); + p = camera.toWorldSpace(p); + expect(mathter::AlmostEqual(p, vecs[2 * i + 1]), errorMessage(p, vecs[2 * i + 1])); } } - void testFrustumClipOccurs(Point points[], Frustum& frustum, int length) { + void testFrustumClipOccurs(Vec3 vecs[], Camera& camera, Frustum& frustum, int length) { for (int i = 0; i < length; i++) { - Point p = points[i]; + Vec3 p = vecs[i]; + p = camera.toCameraSpace(p); frustum.clipToFrustum(p); - expect(p != points[i], errorMessage(p, points[i])); + p = camera.toWorldSpace(p); + expect(!mathter::AlmostEqual(p, vecs[i]), errorMessage(p, vecs[i])); } } }; diff --git a/Source/audio/PerspectiveEffect.cpp b/Source/audio/PerspectiveEffect.cpp index ac3b758..31e08ff 100644 --- a/Source/audio/PerspectiveEffect.cpp +++ b/Source/audio/PerspectiveEffect.cpp @@ -1,6 +1,7 @@ #include "PerspectiveEffect.h" #include #include "../MathUtil.h" +#include "../obj/Camera.h" PerspectiveEffect::PerspectiveEffect(int versionHint) { fixedRotateX = new BooleanParameter("Perspective Fixed Rotate X", "perspectiveFixedRotateX", versionHint, false); @@ -13,7 +14,7 @@ PerspectiveEffect::~PerspectiveEffect() {} Point PerspectiveEffect::apply(int index, Point input, const std::vector& values, double sampleRate) { auto effectScale = values[0]; auto focalLength = juce::jmax(values[1], 0.001); - auto depth = 1.0 + (values[2] - 0.1) * 3; + auto depth = values[2]; auto rotateSpeed = linearSpeedToActualSpeed(values[3]); double baseRotateX, baseRotateY, baseRotateZ; if (fixedRotateX->getBoolValue()) { @@ -67,17 +68,16 @@ Point PerspectiveEffect::apply(int index, Point input, const std::vector Point p = Point(x3, y3, z3); - Frustum frustum = Frustum(focalLength, 1.0, 0.1, 1000); - Point origin = Point(0, 0, -focalLength - depth); - frustum.setCameraOrigin(origin); - frustum.clipToFrustum(p); + Vec3 origin = Vec3(0, 0, -focalLength - depth); + camera.setPosition(origin); + camera.setFocalLength(focalLength); + Vec3 vec = Vec3(p.x, p.y, p.z); - // need to convert point from world space to camera space before projecting - p = p - origin; + Vec3 projected = camera.project(vec); return Point( - (1 - effectScale) * input.x + effectScale * (p.x * focalLength / p.z), - (1 - effectScale) * input.y + effectScale * (p.y * focalLength / p.z), + (1 - effectScale) * input.x + effectScale * projected.x, + (1 - effectScale) * input.y + effectScale * projected.y, 0 ); } diff --git a/Source/audio/PerspectiveEffect.h b/Source/audio/PerspectiveEffect.h index af12fb3..86cbeea 100644 --- a/Source/audio/PerspectiveEffect.h +++ b/Source/audio/PerspectiveEffect.h @@ -3,7 +3,7 @@ #include "../shape/Point.h" #include "../audio/Effect.h" #include "../lua/LuaParser.h" -#include "../obj/Frustum.h" +#include "../obj/Camera.h" class PerspectiveEffect : public EffectApplication { public: @@ -29,4 +29,6 @@ private: } return actualSpeed; } + + Camera camera; }; diff --git a/Source/mathter/CMakeLists.txt b/Source/mathter/CMakeLists.txt new file mode 100644 index 0000000..07ea400 --- /dev/null +++ b/Source/mathter/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(Mathter INTERFACE) + +message("${CMAKE_CURRENT_SOURCE_DIR}/..") +target_include_directories(Mathter INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/..") + +target_sources(Mathter + INTERFACE + "Mathter.natvis" +) + +target_compile_features(Mathter INTERFACE cxx_std_17) + +if (${MATHTER_USE_XSIMD}) + find_package(xsimd) + target_link_libraries(Mathter INTERFACE xsimd) +endif() \ No newline at end of file diff --git a/Source/mathter/Common/Approx.hpp b/Source/mathter/Common/Approx.hpp new file mode 100644 index 0000000..190cf31 --- /dev/null +++ b/Source/mathter/Common/Approx.hpp @@ -0,0 +1,125 @@ +// L============================================================================= +// L This software is distributed under the MIT license. +// L Copyright 2021 Péter Kardos +// L============================================================================= + +#pragma once + +// Specialization for floats. +#include "../IoStream.hpp" +#include "../Matrix.hpp" +#include "../Vector.hpp" + +#include + +namespace mathter { + + +template +bool AlmostEqual(T d1, T d2, std::true_type) { + if (std::abs(d1) < 1e-38 && std::abs(d2) < 1e-38) { + return true; + } + if ((d1 == 0 && d2 < 1e-4) || (d2 == 0 && d1 < 1e-4)) { + return true; + } + T scaler = pow(T(10), floor(std::log10(std::abs(d1)))); + d1 /= scaler; + d2 /= scaler; + d1 *= T(1000.0); + d2 *= T(1000.0); + return round(d1) == round(d2); +} + +// Specialization for int, complex and custom types: simple equality. +template +bool AlmostEqual(T d1, T d2, std::false_type) { + return d1 == d2; +} + +// Check equivalence with tolerance. +template ::value && traits::NotMatrix::value && traits::NotQuaternion::value>> +bool AlmostEqual(T d1, U d2) { + using P = traits::MatMulElemT; + return AlmostEqual(P(d1), P(d2), std::integral_constant::value>()); +} + +template +bool AlmostEqual(const Vector& lhs, const Vector& rhs) { + bool eq = true; + for (auto i : impl::Range(Dim)) { + eq = eq && AlmostEqual(lhs[i], rhs[i]); + } + return eq; +} + +template +bool AlmostEqual(const Quaternion& lhs, const Quaternion& rhs) { + bool eq = true; + for (auto i : impl::Range(4)) { + eq = eq && AlmostEqual(lhs.vec[i], rhs.vec[i]); + } + return eq; +} + +template +bool AlmostEqual(const Matrix& lhs, const Matrix& rhs) { + bool eq = true; + for (auto i : impl::Range(Rows)) { + for (auto j : impl::Range(Columns)) { + eq = eq && AlmostEqual(lhs(i, j), rhs(i, j)); + } + } + return eq; +} + + +// Floating point comparison helper class, works like Catch2 units testing framework's float Approx. +template +struct ApproxHelper { + ApproxHelper() {} + explicit ApproxHelper(LinalgClass object) { + this->object = object; + } + LinalgClass object; +}; + + +template +bool operator==(const ApproxHelper& lhs, const LinalgClass2& rhs) { + return AlmostEqual(lhs.object, rhs); +} + +template +bool operator==(const LinalgClass1& lhs, const ApproxHelper& rhs) { + return AlmostEqual(rhs.object, lhs); +} + +template +bool operator==(const ApproxHelper& lhs, const ApproxHelper& rhs) { + return AlmostEqual(lhs.object, rhs.object); +} + +template +std::ostream& operator<<(std::ostream& os, const ApproxHelper& arg) { + os << arg.object; + return os; +} + +template +ApproxHelper ApproxVec(const LinalgClass& arg) { + return ApproxHelper{ arg }; +} + + + +} // namespace mathter \ No newline at end of file diff --git a/Source/mathter/Common/Definitions.hpp b/Source/mathter/Common/Definitions.hpp new file mode 100644 index 0000000..4a8b1cb --- /dev/null +++ b/Source/mathter/Common/Definitions.hpp @@ -0,0 +1,75 @@ +// L============================================================================= +// L This software is distributed under the MIT license. +// L Copyright 2021 Péter Kardos +// L============================================================================= + +#pragma once + +namespace mathter { + +//------------------------------------------------------------------------------ +// Enums +//------------------------------------------------------------------------------ + +/// Determines if you want to left- or right-multiply your matrices with vectors. +/// +/// This flag affects the generated transformation matrices. If you want to write M2*M1*v in your code, +/// then choose PRECEDE_VECTOR, if you want v*M1*M2, choose FOLLOW_VECTOR. Matrices generated by Transform, Scale, +/// Rotation and similar functions will match your order of multiplication. (I.e. bottom row is translation if you +/// choose FOLLOW_VECTOR). +/// You can still use M*v and v*M in your code. +/// +enum class eMatrixOrder { + PRECEDE_VECTOR, + FOLLOW_VECTOR, +}; + +/// Determines the memory layout of matrices. +/// +/// For ROW_MAJOR layout, the matrix's first row comes first in memory, followed immediately by +/// the second row's elements. For COLUMN_MAJOR matrices, the memory region begins with the first column. +/// This does not affect arithmetic or matrix generator function in any way. Your arithmetic will work +/// the same way if you change this. +/// Please note that changing this flag may affect performance of arithmetic operations. Profile your +/// code to determine optimal settings. Performance may depend on multiple factors. +/// +enum class eMatrixLayout { + ROW_MAJOR, + COLUMN_MAJOR, +}; + + + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +/// Specify this as Vector or Matrix dimension template parameter to set size at runtime. +/// PLEASE NOTE THAT DYNAMICALLY SIZED VECTORS AND MATRICES ARE NOT SUPPORTED YET. +constexpr int DYNAMIC = -1; + + +//------------------------------------------------------------------------------ +// Classes +//------------------------------------------------------------------------------ + +template +struct VectorData; + +template +class Vector; + +template +class Swizzle; + +template +class Matrix; + +template +class SubmatrixHelper; + +template +class Quaternion; + + +} // namespace mathter diff --git a/Source/mathter/Common/DeterministicInitializer.hpp b/Source/mathter/Common/DeterministicInitializer.hpp new file mode 100644 index 0000000..cfa6c26 --- /dev/null +++ b/Source/mathter/Common/DeterministicInitializer.hpp @@ -0,0 +1,112 @@ +// L============================================================================= +// L This software is distributed under the MIT license. +// L Copyright 2021 Péter Kardos +// L============================================================================= + +#pragma once + +#include +#include +#include + +namespace mathter::impl { + + +/// Return a value initialized to zero, or, if not possible, a value-initialized object. +/// If you want special treatment for a particular type, specialize this method. +template +struct NullScalarInitializer { + static constexpr T Get() { + if constexpr (std::is_convertible_v) { + return T(0); + } + else { + return T{}; + } + } +}; + + +/// Return a value initialized to zero, or, if not possible, a value-initialized object. +template +constexpr T NullScalar() { + return NullScalarInitializer::Get(); +} + + +/// Initializes an object of type T to invalid (NaN) value. +/// Returns a signaling NaN, +/// if not possible, a quiet NaN, +/// if not possible, an infinity, +/// if not possible, the highest value for T, +/// if not possible, then a null-initialized value. +/// If you want special treatment for a particular type, specialize this method. +template +struct InvalidScalarInitializer { + static constexpr T Get() { + if constexpr (std::numeric_limits::is_specialized) { + if constexpr (std::numeric_limits::has_signaling_NaN) { + return std::numeric_limits::signaling_NaN(); + } + else if constexpr (std::numeric_limits::has_quiet_NaN) { + return std::numeric_limits::quiet_NaN(); + } + else if constexpr (std::numeric_limits::has_infinity) { + return std::numeric_limits::infinity(); + } + else { + return std::numeric_limits::max(); + } + } + return NullScalar(); + } +}; + + +/// Return a signaling NaN complex number. Same as the unspecialized version. +template +struct InvalidScalarInitializer> { + static constexpr std::complex Get() { + return { InvalidScalarInitializer::Get(), InvalidScalarInitializer::Get() }; + } +}; + + +/// Returns a signaling NaN, +/// if not possible, a quiet NaN, +/// if not possible, an infinity, +/// if not possible, the highest value for T, +/// if not possible, then a null-initialized value. +template +constexpr T InvalidScalar() { + return InvalidScalarInitializer::Get(); +} + + +#if !defined(MATHTER_NULL_INITIALIZE) && !defined(MATHTER_INVALID_INITIALIZE) && !defined(MATHTER_DONT_INITIALIZE) +#ifdef NDEBUG +#define MATHTER_DONT_INITIALIZE 1 +#else +#define MATHTER_INVALID_INITIALIZE 1 +#endif +#endif + + +#if defined(MATHTER_NULL_INITIALIZE) +#define MATHTER_SCALAR_INIT_EXPRESSION(T) mathter::impl::NullScalar() +#elif defined(MATHTER_INVALID_INITIALIZE) +#define MATHTER_SCALAR_INIT_EXPRESSION(T) mathter::impl::InvalidScalar() +#elif defined(MATHTER_DONT_INITIALIZE) +#define MATHTER_SCALAR_INIT_EXPRESSION(T) +#else +#error Set at least one of the MATHTER_NULL/INVALID/DONT_INITIALIZE macros. +#endif + +#if defined(MATHTER_NULL_INITIALIZE) || defined(MATHTER_INVALID_INITIALIZE) +#define MATHTER_VECTOR_INITIALIZER(T) : Vector(MATHTER_SCALAR_INIT_EXPRESSION(T)) +#else +#define MATHTER_VECTOR_INITIALIZER(T) +#endif + + +} // namespace mathter::impl \ No newline at end of file diff --git a/Source/mathter/Common/MathUtil.hpp b/Source/mathter/Common/MathUtil.hpp new file mode 100644 index 0000000..e80f137 --- /dev/null +++ b/Source/mathter/Common/MathUtil.hpp @@ -0,0 +1,34 @@ +// L============================================================================= +// L This software is distributed under the MIT license. +// L Copyright 2021 Péter Kardos +// L============================================================================= + +#pragma once + +#include + +namespace mathter::impl { + + +template +T sign(T arg) { + return T(arg > T(0)) - (arg < T(0)); +} + +template +T sign_nonzero(T arg) { + return std::copysign(T(1), arg); +} + +template +constexpr T ConstexprExp10(int exponent) { + return exponent == 0 ? T(1) : T(10) * ConstexprExp10(exponent - 1); +} + +template +constexpr T ConstexprAbs(T arg) { + return arg >= T(0) ? arg : -arg; +} + + +} // namespace mathter::impl \ No newline at end of file diff --git a/Source/mathter/Common/Range.hpp b/Source/mathter/Common/Range.hpp new file mode 100644 index 0000000..de5ee38 --- /dev/null +++ b/Source/mathter/Common/Range.hpp @@ -0,0 +1,79 @@ +// L============================================================================= +// L This software is distributed under the MIT license. +// L Copyright 2021 Péter Kardos +// L============================================================================= + +#pragma once + +#include +#include + + +namespace mathter::impl { + +// Helper for writing for loops as for (auto i : Range(0,10)) +template +class RangeHelper { +public: + class iterator { + friend class RangeHelper; + iterator(T value, T step) : value(value), step(step) {} + + public: + iterator() : value(std::numeric_limits::lowest()) {} + + using value_type = T; + using difference_type = ptrdiff_t; + using reference = T&; + using pointer = T*; + using iterator_category = std::forward_iterator_tag; + + void operator++() { + value += step; + } + T operator*() const { + return value; + } + bool operator==(const iterator& rhs) const { + return value == rhs.value; + } + bool operator!=(const iterator& rhs) const { + return !(*this == rhs); + } + + private: + T value; + T step; + }; + + RangeHelper(T first, T last, T step) : first(first), last(last), step(step) {} + + iterator begin() const { return iterator(first, step); } + iterator end() const { return iterator(last, step); } + iterator cbegin() const { return iterator(first, step); } + iterator cend() const { return iterator(last, step); } + +private: + T first, last, step; +}; + + +template +RangeHelper Range(T first, T last, T step) { + return RangeHelper(first, last, step); +} + +template +RangeHelper Range(T first, T last) { + T step = last >= first ? T(1) : T(-1); + return Range(first, last, step); +} + +template +RangeHelper Range(T last) { + T first = T(0); + T step = last >= first ? T(1) : T(-1); + return Range(first, last, step); +} + +} // namespace mathter::impl \ No newline at end of file diff --git a/Source/mathter/Common/Traits.hpp b/Source/mathter/Common/Traits.hpp new file mode 100644 index 0000000..8f815fb --- /dev/null +++ b/Source/mathter/Common/Traits.hpp @@ -0,0 +1,309 @@ +// L============================================================================= +// L This software is distributed under the MIT license. +// L Copyright 2021 Péter Kardos +// L============================================================================= + +#pragma once + +#include "Definitions.hpp" + +#include +#include +#include +#include +#include +#include + + +namespace mathter::traits { + +// Vector properties +template +class VectorTraitsHelper {}; + +template +class VectorTraitsHelper> { +public: + using Type = T_; + static constexpr int Dim = Dim_; + static constexpr bool Packed = Packed_; +}; + +template +class VectorTraitsHelper> { +public: + using Type = T_; + static constexpr int Dim = Dim_; + static constexpr bool Packed = Packed_; +}; + +template +class VectorTraits : public VectorTraitsHelper::type> {}; + + +// Matrix properties +template +class MatrixTraitsHelper {}; + +template +class MatrixTraitsHelper> { +public: + using Type = T_; + static constexpr int Rows = Rows_; + static constexpr int Columns = Columns_; + static constexpr eMatrixOrder Order = Order_; + static constexpr eMatrixLayout Layout = Layout_; + static constexpr bool Packed = Packed_; +}; + +template +class MatrixTraits : public MatrixTraitsHelper::type> {}; + + +template +class OppositeOrder { +public: + static constexpr eMatrixOrder value = (Order == eMatrixOrder::FOLLOW_VECTOR ? eMatrixOrder::PRECEDE_VECTOR : eMatrixOrder::FOLLOW_VECTOR); +}; + +template +class OppositeLayout { +public: + static constexpr eMatrixLayout value = (Layout == eMatrixLayout::ROW_MAJOR ? eMatrixLayout::COLUMN_MAJOR : eMatrixLayout::ROW_MAJOR); +}; + + +// Common utility +template +using MatMulElemT = decltype(T() * U() + T() * U()); + + + +// Template metaprogramming utilities +template