From d261293d6baba80c346ffd6de21cdc6a5f5cdb21 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Mon, 4 Aug 2025 10:47:24 +0300 Subject: [PATCH] Separate coord space transform class for more correct scaling --- CMakeLists.txt | 1 + include/Helpers/FixedPoint.hpp | 198 ++++++++++++++++++ src/CoordSpaceHelper.hpp | 4 +- src/CoordSpaceHelperTransform.cpp | 63 ++++++ src/CoordSpaceHelperTransform.hpp | 35 ++++ .../DrawingProgramSelection.cpp | 8 +- .../DrawingProgramSelection.hpp | 5 +- 7 files changed, 307 insertions(+), 7 deletions(-) create mode 100644 src/CoordSpaceHelperTransform.cpp create mode 100644 src/CoordSpaceHelperTransform.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d166c5..06979cc 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ set(sources "src/main.cpp" "src/DrawCamera.cpp" "src/DrawCollision.cpp" "src/CoordSpaceHelper.cpp" + "src/CoordSpaceHelperTransform.cpp" "src/ResourceManager.cpp" "src/Toolbar.cpp" "src/World.cpp" diff --git a/include/Helpers/FixedPoint.hpp b/include/Helpers/FixedPoint.hpp index fed132f..d1b63d1 100644 --- a/include/Helpers/FixedPoint.hpp +++ b/include/Helpers/FixedPoint.hpp @@ -318,9 +318,191 @@ namespace FixedPoint { private: T val; }; + + template class Multiplier { + public: + typedef Number UnderlyingNumber; + + Multiplier(): + val(1), + isReciprocal(false) + {} + + Multiplier(const UnderlyingNumber& initVal): + val(initVal), + isReciprocal(false) + { + if(val != UnderlyingNumber(0) && abs(val) < UnderlyingNumber(1)) { + val = UnderlyingNumber(1) / val; + isReciprocal = true; + } + } + + Multiplier(const UnderlyingNumber& initVal, bool isReciprocal): + val(initVal), + isReciprocal(isReciprocal) + { + if(abs(val) == UnderlyingNumber(1)) + isReciprocal = false; + } + + explicit Multiplier(double initVal) { + if(std::abs(initVal) < 1.0) { + val = UnderlyingNumber(1.0 / initVal); + isReciprocal = true; + } + else { + val = UnderlyingNumber(initVal); + isReciprocal = false; + } + } + + explicit Multiplier(float initVal) { + if(std::abs(initVal) < 1.0) { + val = UnderlyingNumber(1.0 / initVal); + isReciprocal = true; + } + else { + val = UnderlyingNumber(initVal); + isReciprocal = false; + } + } + + explicit Multiplier(int64_t initVal) { + val = UnderlyingNumber(initVal); + isReciprocal = false; + } + + explicit Multiplier(int32_t initVal) { + val = UnderlyingNumber(initVal); + isReciprocal = false; + } + + explicit Multiplier(int16_t initVal) { + val = UnderlyingNumber(initVal); + isReciprocal = false; + } + + explicit Multiplier(uint64_t initVal) { + val = UnderlyingNumber(initVal); + isReciprocal = false; + } + + explicit Multiplier(uint32_t initVal) { + val = UnderlyingNumber(initVal); + isReciprocal = false; + } + + explicit Multiplier(uint16_t initVal) { + val = UnderlyingNumber(initVal); + isReciprocal = false; + } + + explicit operator float() const { + if(isReciprocal) + return 1.0f / static_cast(val); + else + return static_cast(val); + } + + explicit operator double() const { + if(isReciprocal) + return 1.0 / static_cast(val); + else + return static_cast(val); + } + + explicit operator int64_t() const { + if(isReciprocal) + return 0; + else + return static_cast(val); + } + + explicit operator uint64_t() const { + if(isReciprocal) + return 0; + else + return static_cast(val); + } + + explicit operator UnderlyingNumber() const { + if(isReciprocal) + return UnderlyingNumber(1) / val; + else + return val; + } + + Multiplier multiply_double(double a) const { + return (*this) * Multiplier(a); + } + + Multiplier divide_double(double a) const { + return (*this) / Multiplier(a); + } + + Multiplier operator*(const Multiplier& a) const { + return (*this) / Multiplier(a.val, !a.isReciprocal); + } + + Multiplier operator/(const Multiplier& a) const { + if(val == UnderlyingNumber(0)) + return Multiplier(0); + + if(!isReciprocal && !a.isReciprocal) { + if(abs(val) < abs(a.val)) + return Multiplier(a.val / val, true); + else + return Multiplier(val / a.val, false); + } + else if(!isReciprocal && a.isReciprocal) { + return Multiplier(val * a.val, false); + } + else if(isReciprocal && !a.isReciprocal) { + if(abs(val) < abs(a.val)) + return Multiplier(a.val / val, false); + else + return Multiplier(val / a.val, true); + } + else { + if(abs(val) < abs(a.val)) + return Multiplier(a.val / val, false); + else + return Multiplier(val / a.val, true); + } + } + + bool operator==(const Multiplier& other) const { + return (val == other.val) && (isReciprocal == other.isReciprocal || val == UnderlyingNumber(1)); + } + + bool operator!=(const Multiplier& other) const { + return !((*this) == other); + } + private: + Number val; + bool isReciprocal; + }; + + template Mult::UnderlyingNumber operator*(const Mult& a, const typename Mult::UnderlyingNumber& b) { + return static_cast(a * Multiplier(b)); + } + + template Mult::UnderlyingNumber operator*(const typename Mult::UnderlyingNumber& b, const Mult& a) { + return static_cast(a * Multiplier(b)); + } + + template Mult::UnderlyingNumber operator/(const Mult& a, const typename Mult::UnderlyingNumber& b) { + return static_cast(a / Multiplier(b)); + } + + template Mult::UnderlyingNumber operator/(const typename Mult::UnderlyingNumber& a, const Mult& b) { + return static_cast(Multiplier(a) / b); + } } typedef FixedPoint::Number WorldScalar; +typedef FixedPoint::Multiplier WorldMultiplier; namespace Eigen { template<> struct NumTraits : GenericNumTraits @@ -344,3 +526,19 @@ namespace Eigen { }; }; } + +namespace FixedPoint { + template Eigen::Vector multiplier_vec_mult(const Eigen::Vector& a, const Mult& m) { + auto toRet = a; + toRet.x() = toRet.x() * m; + toRet.y() = toRet.y() * m; + return toRet; + } + + template Eigen::Vector multiplier_vec_div(const Eigen::Vector& a, const Mult& m) { + auto toRet = a; + toRet.x() = toRet.x() / m; + toRet.y() = toRet.y() / m; + return toRet; + } +} diff --git a/src/CoordSpaceHelper.hpp b/src/CoordSpaceHelper.hpp index 45deaaa..8ac17a2 100644 --- a/src/CoordSpaceHelper.hpp +++ b/src/CoordSpaceHelper.hpp @@ -12,6 +12,8 @@ class World; struct DrawData; +WorldVec rotate_world_coord(const WorldVec& a, double rotationAngle); + class CoordSpaceHelper { public: template void save(Archive& a) const { @@ -82,8 +84,8 @@ class CoordSpaceHelper { void translate(const WorldVec& translation); void scale(const WorldScalar& scaleAmount); void rotate_about(const WorldVec& rotatePos, double angle); - void scale_about_double(const WorldVec& scalePos, double scaleAmount); void scale_about(const WorldVec& scalePos, const WorldScalar& scaleAmount, bool flipScale); + void scale_about_double(const WorldVec& scalePos, double scaleAmount); void set_rotation(double newRotation); template std::array to_space(const std::array& coord) const { diff --git a/src/CoordSpaceHelperTransform.cpp b/src/CoordSpaceHelperTransform.cpp new file mode 100644 index 0000000..722fe42 --- /dev/null +++ b/src/CoordSpaceHelperTransform.cpp @@ -0,0 +1,63 @@ +#include "CoordSpaceHelperTransform.hpp" + +bool CoordSpaceHelperTransform::operator==(const CoordSpaceHelperTransform& otherCoords) const { + return rotation == otherCoords.rotation && pos == otherCoords.pos && inverseScale == otherCoords.inverseScale; +} + +bool CoordSpaceHelperTransform::operator!=(const CoordSpaceHelperTransform& otherCoords) const { + return rotation != otherCoords.rotation || pos != otherCoords.pos || inverseScale != otherCoords.inverseScale; +} + +WorldVec CoordSpaceHelperTransform::to_space_world(const WorldVec& coord) const { + return rotate_world_coord(FixedPoint::multiplier_vec_div((coord - pos), inverseScale), -rotation); +} + +WorldVec CoordSpaceHelperTransform::from_space_world(const WorldVec& coord) const { + return FixedPoint::multiplier_vec_mult(rotate_world_coord(coord, rotation), inverseScale) + pos; +} + +CoordSpaceHelper CoordSpaceHelperTransform::other_coord_space_to_this_space(const CoordSpaceHelper& other) const { + CoordSpaceHelper toRet; + toRet.pos = to_space_world(other.pos); + toRet.rotation = other.rotation - rotation; + toRet.inverseScale = other.inverseScale / inverseScale; + return toRet; +} + +CoordSpaceHelper CoordSpaceHelperTransform::other_coord_space_from_this_space(const CoordSpaceHelper& other) const { + CoordSpaceHelper toRet; + toRet.pos = from_space_world(other.pos); + toRet.rotation = other.rotation + rotation; + toRet.inverseScale = other.inverseScale * inverseScale; + return toRet; +} + +void CoordSpaceHelperTransform::translate(const WorldVec& translation) { + pos += translation; +} + +void CoordSpaceHelperTransform::rotate_about(const WorldVec& rotatePos, double angle) { + WorldVec a = pos - rotatePos; + FixedPoint::Number s = FixedPoint::Number(std::sin(angle)); + FixedPoint::Number c = FixedPoint::Number(std::cos(angle)); + set_rotation(rotation + angle); + pos = { + rotatePos.x() + a.x().multiply_different_precision(c) - a.y().multiply_different_precision(s), + rotatePos.y() + a.x().multiply_different_precision(s) + a.y().multiply_different_precision(c) + }; +} + +void CoordSpaceHelperTransform::set_rotation(double newRotation) { + rotation = newRotation; + // using circular fmod caused inaccuracies, so these loops might be a better solution + while(rotation >= 2.0 * std::numbers::pi) + rotation -= 2.0 * std::numbers::pi; + while (rotation < 0.0) + rotation += 2.0 * std::numbers::pi; +} + +void CoordSpaceHelperTransform::scale_about(const WorldVec& scalePos, const WorldMultiplier& scaleAmount) { + inverseScale = inverseScale / scaleAmount; + WorldVec mVec = pos - scalePos; + pos = scalePos + FixedPoint::multiplier_vec_div(mVec, scaleAmount); +} diff --git a/src/CoordSpaceHelperTransform.hpp b/src/CoordSpaceHelperTransform.hpp new file mode 100644 index 0000000..c23e257 --- /dev/null +++ b/src/CoordSpaceHelperTransform.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "SharedTypes.hpp" +#include +#include +#include +#include "CoordSpaceHelper.hpp" + +#ifndef IS_SERVER +#include "include/core/SkMatrix.h" +#include "include/core/SkCanvas.h" +#endif + +class World; +struct DrawData; + +class CoordSpaceHelperTransform { + public: + WorldVec from_space_world(const WorldVec& coord) const; + WorldVec to_space_world(const WorldVec& coord) const; + + CoordSpaceHelper other_coord_space_to_this_space(const CoordSpaceHelper& other) const; + CoordSpaceHelper other_coord_space_from_this_space(const CoordSpaceHelper& other) const; + + bool operator==(const CoordSpaceHelperTransform& otherCoords) const; + bool operator!=(const CoordSpaceHelperTransform& otherCoords) const; + + void set_rotation(double newRotation); + void translate(const WorldVec& translation); + void rotate_about(const WorldVec& rotatePos, double angle); + void scale_about(const WorldVec& scalePos, const WorldMultiplier& scaleAmount); + + WorldVec pos{0, 0}; + WorldMultiplier inverseScale{1}; + double rotation = 0.0; +}; diff --git a/src/DrawingProgram/DrawingProgramSelection.cpp b/src/DrawingProgram/DrawingProgramSelection.cpp index e70e91e..217cec2 100644 --- a/src/DrawingProgram/DrawingProgramSelection.cpp +++ b/src/DrawingProgram/DrawingProgramSelection.cpp @@ -283,7 +283,7 @@ void DrawingProgramSelection::update() { scaleAmount = centerToScaleStartNorm / centerToScaleCurrentNorm; selectionTransformCoords = startingSelectionTransformCoords; - selectionTransformCoords.scale_about(scaleData.centerPos, scaleAmount, flipScale); + selectionTransformCoords.scale_about(scaleData.centerPos, WorldMultiplier(scaleAmount, flipScale)); } } else { @@ -313,9 +313,9 @@ void DrawingProgramSelection::update() { deselect_all(); else if(drawP.world.main.input.key(InputManager::KEY_DRAW_DELETE).pressed) delete_all(); - else if(drawP.world.main.input.key(InputManager::KEY_COPY).pressed && startingSelectionTransformCoords == CoordSpaceHelper()) + else if(drawP.world.main.input.key(InputManager::KEY_COPY).pressed && startingSelectionTransformCoords == CoordSpaceHelperTransform()) selection_to_clipboard(); - else if(drawP.world.main.input.key(InputManager::KEY_CUT).pressed && startingSelectionTransformCoords == CoordSpaceHelper()) { + else if(drawP.world.main.input.key(InputManager::KEY_CUT).pressed && startingSelectionTransformCoords == CoordSpaceHelperTransform()) { selection_to_clipboard(); delete_all(); } @@ -448,7 +448,7 @@ void DrawingProgramSelection::reset_selection_data() { } void DrawingProgramSelection::reset_transform_data() { - selectionTransformCoords = CoordSpaceHelper(); + selectionTransformCoords = CoordSpaceHelperTransform(); transformOpHappening = TransformOperation::NONE; translateData = TranslationData(); scaleData = ScaleData(); diff --git a/src/DrawingProgram/DrawingProgramSelection.hpp b/src/DrawingProgram/DrawingProgramSelection.hpp index 62fed5c..8e7e603 100644 --- a/src/DrawingProgram/DrawingProgramSelection.hpp +++ b/src/DrawingProgram/DrawingProgramSelection.hpp @@ -1,5 +1,6 @@ #pragma once #include "DrawingProgramCache.hpp" +#include "../CoordSpaceHelperTransform.hpp" class DrawingProgram; @@ -40,7 +41,7 @@ class DrawingProgramSelection { std::array selectionRectPoints; - CoordSpaceHelper selectionTransformCoords; + CoordSpaceHelperTransform selectionTransformCoords; std::unordered_set selectedSet; DrawingProgramCache cache; DrawingProgram& drawP; @@ -53,7 +54,7 @@ class DrawingProgramSelection { ROTATE } transformOpHappening = TransformOperation::NONE; - CoordSpaceHelper startingSelectionTransformCoords; + CoordSpaceHelperTransform startingSelectionTransformCoords; struct TranslationData { WorldVec startPos;