소스 검색

Minor API update, bug fixes, cleanup.

* Remove undefined behavior caused by volatile variables in WorkQueue (volatile variables are not required to be thread-safe).
* Fix nasty DX11 bug caused by poor hash functions and likely hash collision in ShaderVariation.
* Relaxed FlagSet usage requirements.
* Various API extensions and code simplification.
* Const-correctness fixes.
Eugene Kozlov 5 년 전
부모
커밋
18c7230eb7

+ 1 - 1
Source/Urho3D/AngelScript/GraphicsAPI.cpp

@@ -456,7 +456,7 @@ static void RegisterTextures(asIScriptEngine* engine)
     engine->RegisterObjectBehaviour("Viewport", asBEHAVE_FACTORY, "Viewport@+ f()", asFUNCTION(ConstructViewport), asCALL_CDECL);
     engine->RegisterObjectBehaviour("Viewport", asBEHAVE_FACTORY, "Viewport@+ f(Scene@+, Camera@+, RenderPath@+ renderPath = null)", asFUNCTION(ConstructViewportSceneCamera), asCALL_CDECL);
     engine->RegisterObjectBehaviour("Viewport", asBEHAVE_FACTORY, "Viewport@+ f(Scene@+, Camera@+, const IntRect&in, RenderPath@+ renderPath = null)", asFUNCTION(ConstructViewportSceneCameraRect), asCALL_CDECL);
-    engine->RegisterObjectMethod("Viewport", "void SetRenderPath(XMLFile@+)", asMETHODPR(Viewport, SetRenderPath, (XMLFile*), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Viewport", "bool SetRenderPath(XMLFile@+)", asMETHODPR(Viewport, SetRenderPath, (XMLFile*), bool), asCALL_THISCALL);
     engine->RegisterObjectMethod("Viewport", "void set_scene(Scene@+)", asMETHOD(Viewport, SetScene), asCALL_THISCALL);
     engine->RegisterObjectMethod("Viewport", "Scene@+ get_scene() const", asMETHOD(Viewport, GetScene), asCALL_THISCALL);
     engine->RegisterObjectMethod("Viewport", "void set_camera(Camera@+)", asMETHOD(Viewport, SetCamera), asCALL_THISCALL);

+ 14 - 42
Source/Urho3D/Container/FlagSet.h

@@ -29,23 +29,20 @@
 namespace Urho3D
 {
 
-/// Type trait which enables Enum to be used as FlagSet template parameter. Bitwise operators (| & ^ ~) over enabled Enum will result in FlagSet<Enum>.
-template <typename T> struct IsFlagSet
-{
-    constexpr static bool value_ = false;
-};
-
-/// Enable enum for using in FlagSet. Shall be called within Urho3D namespace.
-#define URHO3D_ENABLE_FLAGSET(enumName) \
-    template<> struct IsFlagSet<enumName> { constexpr static bool value_ = true; } \
-
-/// Enable enum for using in FlagSet and declare FlagSet specialization. Shall be called within Urho3D namespace.
+/// Make bitwise operators (| & ^ ~) automatically construct FlagSet from Enum.
+#define URHO3D_AUTOMATIC_FLAGSET(Enum) \
+    inline Urho3D::FlagSet<Enum> operator | (const Enum lhs, const Enum rhs) { return Urho3D::FlagSet<Enum>(lhs) | rhs; } \
+    inline Urho3D::FlagSet<Enum> operator & (const Enum lhs, const Enum rhs) { return Urho3D::FlagSet<Enum>(lhs) & rhs; } \
+    inline Urho3D::FlagSet<Enum> operator ^ (const Enum lhs, const Enum rhs) { return Urho3D::FlagSet<Enum>(lhs) ^ rhs; } \
+    inline Urho3D::FlagSet<Enum> operator ~ (const Enum rhs) { return ~Urho3D::FlagSet<Enum>(rhs); }
+
+/// Declare FlagSet for specific enum and create operators for automatic FlagSet construction.
 #define URHO3D_FLAGSET(enumName, flagsetName) \
-    URHO3D_ENABLE_FLAGSET(enumName); \
-    using flagsetName = FlagSet<enumName>
+    URHO3D_AUTOMATIC_FLAGSET(enumName); \
+    using flagsetName = Urho3D::FlagSet<enumName>
 
 /// A set of flags defined by an Enum.
-template <class E, class = typename std::enable_if<IsFlagSet<E>::value_>::type>
+template <class E>
 class FlagSet
 {
 public:
@@ -231,37 +228,12 @@ public:
     /// Return underlying integer (non-constant).
     Integer& AsInteger() { return value_; }
 
+    /// Return hash value.
+    unsigned ToHash() const { return static_cast<unsigned>(value_); }
+
 protected:
     /// Value
     Integer value_ = 0;
 };
 
 }
-
-/// Bitwise Operator OR for against Enum values
-template <class Enum, class = typename std::enable_if<Urho3D::IsFlagSet<Enum>::value_>::type>
-Urho3D::FlagSet<Enum> operator |(const Enum lhs, const Enum rhs)
-{
-    return Urho3D::FlagSet<Enum>(lhs) | rhs;
-}
-
-/// Bitwise Operator AND for against Enum values
-template <class Enum, class = typename std::enable_if<Urho3D::IsFlagSet<Enum>::value_>::type>
-Urho3D::FlagSet<Enum> operator & (const Enum lhs, const Enum rhs)
-{
-    return Urho3D::FlagSet<Enum>(lhs) & rhs;
-}
-
-/// Bitwise Operator XOR for against Enum values
-template <class Enum, class = typename std::enable_if<Urho3D::IsFlagSet<Enum>::value_>::type>
-Urho3D::FlagSet<Enum> operator ^ (const Enum lhs, const Enum rhs)
-{
-    return Urho3D::FlagSet<Enum>(lhs) ^ rhs;
-}
-
-/// Bitwise Operator INVERSION for against Enum values
-template <class Enum, class = typename std::enable_if<Urho3D::IsFlagSet<Enum>::value_>::type>
-Urho3D::FlagSet<Enum> operator ~ (const Enum rhs)
-{
-    return ~Urho3D::FlagSet<Enum>(rhs);
-}

+ 6 - 0
Source/Urho3D/Container/Hash.h

@@ -27,6 +27,12 @@
 namespace Urho3D
 {
 
+/// Combine hash into result value.
+inline void CombineHash(unsigned& result, unsigned hash)
+{
+    result ^= hash + 0x9e3779b9 + (result << 6) + (result >> 2);
+}
+
 /// Pointer hash function.
 template <class T> unsigned MakeHash(T* value)
 {

+ 58 - 13
Source/Urho3D/Container/Ptr.h

@@ -55,6 +55,13 @@ public:
         AddRef();
     }
 
+    /// Move-construct from another shared pointer.
+    SharedPtr(SharedPtr<T>&& rhs) noexcept :
+        ptr_(rhs.ptr_)
+    {
+        rhs.ptr_ = nullptr;
+    }
+
     /// Copy-construct from another shared pointer allowing implicit upcasting.
     template <class U> SharedPtr(const SharedPtr<U>& rhs) noexcept :    // NOLINT(google-explicit-constructor)
         ptr_(rhs.ptr_)
@@ -87,6 +94,15 @@ public:
         return *this;
     }
 
+    /// Move-assign from another shared pointer.
+    SharedPtr<T>& operator =(SharedPtr<T>&& rhs)
+    {
+        SharedPtr<T> copy(std::move(rhs));
+        Swap(copy);
+
+        return *this;
+    }
+
     /// Assign from another shared pointer allowing implicit upcasting.
     template <class U> SharedPtr<T>& operator =(const SharedPtr<U>& rhs)
     {
@@ -145,10 +161,14 @@ public:
     operator T*() const { return ptr_; }    // NOLINT(google-explicit-constructor)
 
     /// Swap with another SharedPtr.
-    void Swap(SharedPtr& rhs) { Urho3D::Swap(ptr_, rhs.ptr_); }
+    void Swap(SharedPtr<T>& rhs) { Urho3D::Swap(ptr_, rhs.ptr_); }
 
-    /// Reset to null and release the object reference.
-    void Reset() { ReleaseRef(); }
+    /// Reset with another pointer.
+    void Reset(T* ptr = nullptr)
+    {
+        SharedPtr<T> copy(ptr);
+        Swap(copy);
+    }
 
     /// Detach without destroying the object even if the refcount goes zero. To be used for scripting language interoperation.
     T* Detach()
@@ -265,6 +285,15 @@ public:
         AddRef();
     }
 
+    /// Move-construct from another weak pointer.
+    WeakPtr(WeakPtr<T>&& rhs) noexcept :
+        ptr_(rhs.ptr_),
+        refCount_(rhs.refCount_)
+    {
+        rhs.ptr_ = nullptr;
+        rhs.refCount_ = nullptr;
+    }
+
     /// Copy-construct from another weak pointer allowing implicit upcasting.
     template <class U> WeakPtr(const WeakPtr<U>& rhs) noexcept :   // NOLINT(google-explicit-constructor)
         ptr_(rhs.ptr_),
@@ -301,10 +330,8 @@ public:
         if (ptr_ == rhs.Get() && refCount_ == rhs.RefCountPtr())
             return *this;
 
-        ReleaseRef();
-        ptr_ = rhs.Get();
-        refCount_ = rhs.RefCountPtr();
-        AddRef();
+        WeakPtr<T> copy(rhs);
+        Swap(copy);
 
         return *this;
     }
@@ -315,10 +342,17 @@ public:
         if (ptr_ == rhs.ptr_ && refCount_ == rhs.refCount_)
             return *this;
 
-        ReleaseRef();
-        ptr_ = rhs.ptr_;
-        refCount_ = rhs.refCount_;
-        AddRef();
+        WeakPtr<T> copy(rhs);
+        Swap(copy);
+
+        return *this;
+    }
+
+    /// Move-assign from another weak pointer.
+    WeakPtr<T>& operator =(WeakPtr<T>&& rhs)
+    {
+        WeakPtr<T> copy(std::move(rhs));
+        Swap(copy);
 
         return *this;
     }
@@ -404,8 +438,19 @@ public:
     /// Convert to a raw pointer, null if the object is expired.
     operator T*() const { return Get(); }   // NOLINT(google-explicit-constructor)
 
-    /// Reset to null and release the weak reference.
-    void Reset() { ReleaseRef(); }
+    /// Swap with another WeakPtr.
+    void Swap(WeakPtr<T>& rhs)
+    {
+        Urho3D::Swap(ptr_, rhs.ptr_);
+        Urho3D::Swap(refCount_, rhs.refCount_);
+    }
+
+    /// Reset with another pointer.
+    void Reset(T* ptr = nullptr)
+    {
+        WeakPtr<T> copy(ptr);
+        Swap(copy);
+    }
 
     /// Perform a static cast from a weak pointer of another type.
     template <class U> void StaticCast(const WeakPtr<U>& rhs)

+ 5 - 3
Source/Urho3D/Core/WorkQueue.h

@@ -26,6 +26,8 @@
 #include "../Core/Mutex.h"
 #include "../Core/Object.h"
 
+#include <atomic>
+
 namespace Urho3D
 {
 
@@ -56,7 +58,7 @@ public:
     /// Whether to send event on completion.
     bool sendEvent_{};
     /// Completed flag.
-    volatile bool completed_{};
+    std::atomic<bool> completed_{};
 
 private:
     bool pooled_{};
@@ -135,9 +137,9 @@ private:
     /// Worker queue mutex.
     Mutex queueMutex_;
     /// Shutting down flag.
-    volatile bool shutDown_;
+    std::atomic<bool> shutDown_;
     /// Pausing flag. Indicates the worker threads should not contend for the queue mutex.
-    volatile bool pausing_;
+    std::atomic<bool> pausing_;
     /// Paused flag. Indicates the queue mutex being locked to prevent worker threads using up CPU time.
     bool paused_;
     /// Completing work in the main thread flag.

+ 4 - 2
Source/Urho3D/Graphics/Direct3D11/D3D11ShaderVariation.cpp

@@ -350,6 +350,7 @@ void ShaderVariation::ParseParameters(unsigned char* bufData, unsigned bufSize)
 
     if (type_ == VS)
     {
+        unsigned elementHash = 0;
         for (unsigned i = 0; i < shaderDesc.InputParameters; ++i)
         {
             D3D11_SIGNATURE_PARAMETER_DESC paramDesc;
@@ -357,10 +358,11 @@ void ShaderVariation::ParseParameters(unsigned char* bufData, unsigned bufSize)
             VertexElementSemantic semantic = (VertexElementSemantic)GetStringListIndex(paramDesc.SemanticName, elementSemanticNames, MAX_VERTEX_ELEMENT_SEMANTICS, true);
             if (semantic != MAX_VERTEX_ELEMENT_SEMANTICS)
             {
-                elementHash_ <<= 4;
-                elementHash_ += ((int)semantic + 1) * (paramDesc.SemanticIndex + 1);
+                CombineHash(elementHash, semantic);
+                CombineHash(elementHash, paramDesc.SemanticIndex);
             }
         }
+        elementHash_ = elementHash;
         elementHash_ <<= 32;
     }
 

+ 5 - 1
Source/Urho3D/Graphics/Viewport.cpp

@@ -103,11 +103,15 @@ void Viewport::SetRenderPath(RenderPath* renderPath)
     }
 }
 
-void Viewport::SetRenderPath(XMLFile* file)
+bool Viewport::SetRenderPath(XMLFile* file)
 {
     SharedPtr<RenderPath> newRenderPath(new RenderPath());
     if (newRenderPath->Load(file))
+    {
         renderPath_ = newRenderPath;
+        return true;
+    }
+    return false;
 }
 
 Scene* Viewport::GetScene() const

+ 1 - 1
Source/Urho3D/Graphics/Viewport.h

@@ -61,7 +61,7 @@ public:
     /// Set rendering path.
     void SetRenderPath(RenderPath* renderPath);
     /// Set rendering path from an XML file.
-    void SetRenderPath(XMLFile* file);
+    bool SetRenderPath(XMLFile* file);
     /// Set whether to render debug geometry. Default true.
     void SetDrawDebug(bool enable);
     /// Set separate camera to use for culling. Sharing a culling camera between several viewports allows to prepare the view only once, saving in CPU use. The culling camera's frustum should cover all the viewport cameras' frusta or else objects may be missing from the rendered view.

+ 1 - 1
Source/Urho3D/LuaScript/pkgs/Graphics/Viewport.pkg

@@ -12,7 +12,7 @@ class Viewport
     void SetCullCamera(Camera* camera);
     void SetRect(const IntRect& rect);
     void SetRenderPath(RenderPath* path);
-    void SetRenderPath(XMLFile* file);
+    bool SetRenderPath(XMLFile* file);
     void SetDrawDebug(bool enable);
     
     Scene* GetScene() const;

+ 21 - 0
Source/Urho3D/Math/Color.cpp

@@ -40,6 +40,16 @@ unsigned Color::ToUInt() const
     return (a << 24u) | (b << 16u) | (g << 8u) | r;
 }
 
+unsigned Color::ToUIntMask(const ChannelMask& mask) const
+{
+    const auto max = static_cast<double>(M_MAX_UNSIGNED);
+    const auto r = static_cast<unsigned>(Clamp(static_cast<double>(r_) * mask.r_, 0.0, max)) & mask.r_;
+    const auto g = static_cast<unsigned>(Clamp(static_cast<double>(g_) * mask.g_, 0.0, max)) & mask.g_;
+    const auto b = static_cast<unsigned>(Clamp(static_cast<double>(b_) * mask.b_, 0.0, max)) & mask.b_;
+    const auto a = static_cast<unsigned>(Clamp(static_cast<double>(a_) * mask.a_, 0.0, max)) & mask.a_;
+    return r | g | b | a;
+}
+
 Vector3 Color::ToHSL() const
 {
     float min, max;
@@ -72,6 +82,15 @@ void Color::FromUInt(unsigned color)
     r_ = ((color >> 0u)  & 0xffu) / 255.0f;
 }
 
+void Color::FromUIntMask(unsigned color, const ChannelMask& mask)
+{
+    // Channel offset is irrelevant during division, but double should be used to avoid precision loss.
+    r_ = !mask.r_ ? 0.0f : static_cast<float>((color & mask.r_) / static_cast<double>(mask.r_));
+    g_ = !mask.g_ ? 0.0f : static_cast<float>((color & mask.g_) / static_cast<double>(mask.g_));
+    b_ = !mask.b_ ? 0.0f : static_cast<float>((color & mask.b_) / static_cast<double>(mask.b_));
+    a_ = !mask.a_ ? 1.0f : static_cast<float>((color & mask.a_) / static_cast<double>(mask.a_));
+}
+
 void Color::FromHSL(float h, float s, float l, float a)
 {
     float c;
@@ -340,6 +359,8 @@ void Color::FromHCM(float h, float c, float m)
 }
 
 
+const Color::ChannelMask Color::ABGR{ 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 };
+const Color::ChannelMask Color::ARGB{ 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 };
 const Color Color::WHITE;
 const Color Color::GRAY(0.5f, 0.5f, 0.5f);
 const Color Color::BLACK(0.0f, 0.0f, 0.0f);

+ 63 - 0
Source/Urho3D/Math/Color.h

@@ -33,6 +33,23 @@ class String;
 class URHO3D_API Color
 {
 public:
+    /// Mask describing color channels.
+    struct ChannelMask
+    {
+        /// Red channel mask. If zero, red channel is set to 0.
+        unsigned r_;
+        /// Green channel mask. If zero, green channel is set to 0.
+        unsigned g_;
+        /// Blue channel mask. If zero, blue channel is set to 0.
+        unsigned b_;
+        /// Alpha channel mask. If zero, alpha channel is set to 1.
+        unsigned a_;
+    };
+    /// Mask for 0xAABBGGRR layout.
+    static const ChannelMask ABGR;
+    /// Mask for 0xAARRGGBB layout.
+    static const ChannelMask ARGB;
+
     /// Construct with default values (opaque white.)
     Color() noexcept :
         r_(1.0f),
@@ -81,6 +98,15 @@ public:
     {
     }
 
+    /// Construct from 32-bit integer. Default format is 0xAABBGGRR.
+    explicit Color(unsigned color, const ChannelMask& mask = ABGR) { FromUIntMask(color, mask); }
+
+    /// Construct from 3-vector.
+    explicit Color(const Vector3& color) : Color(color.x_, color.y_, color.z_) {}
+
+    /// Construct from 4-vector.
+    explicit Color(const Vector4& color) : Color(color.x_, color.y_, color.z_, color.w_) {}
+
     /// Assign from another color.
     Color& operator =(const Color& rhs) noexcept = default;
 
@@ -117,12 +143,16 @@ public:
 
     /// Return color packed to a 32-bit integer, with R component in the lowest 8 bits. Components are clamped to [0, 1] range.
     unsigned ToUInt() const;
+    /// Return color packed to a 32-bit integer with arbitrary channel mask. Components are clamped to [0, 1] range.
+    unsigned ToUIntMask(const ChannelMask& mask) const;
     /// Return HSL color-space representation as a Vector3; the RGB values are clipped before conversion but not changed in the process.
     Vector3 ToHSL() const;
     /// Return HSV color-space representation as a Vector3; the RGB values are clipped before conversion but not changed in the process.
     Vector3 ToHSV() const;
     /// Set RGBA values from packed 32-bit integer, with R component in the lowest 8 bits (format 0xAABBGGRR).
     void FromUInt(unsigned color);
+    /// Set RGBA values from packed 32-bit integer with arbitrary channel mask.
+    void FromUIntMask(unsigned color, const ChannelMask& mask);
     /// Set RGBA values from specified HSL values and alpha.
     void FromHSL(float h, float s, float l, float a = 1.0f);
     /// Set RGBA values from specified HSV values and alpha.
@@ -155,6 +185,36 @@ public:
     /// Return value as defined for HSV: largest value of the RGB components. Equivalent to calling MinRGB().
     float Value() const { return MaxRGB(); }
 
+    /// Convert single component of the color from gamma to linear space.
+    static float ConvertGammaToLinear(float value)
+    {
+        if (value <= 0.04045f)
+            return value / 12.92f;
+        else if (value < 1.0f)
+            return Pow((value + 0.055f) / 1.055f, 2.4f);
+        else
+            return Pow(value, 2.2f);
+    }
+
+    /// Convert single component of the color from linear to gamma space.
+    static float ConvertLinearToGamma(float value)
+    {
+        if (value <= 0.0f)
+            return 0.0f;
+        else if (value <= 0.0031308f)
+            return 12.92f * value;
+        else if (value < 1.0f)
+            return 1.055f * Pow(value, 0.4166667f) - 0.055f;
+        else
+            return Pow(value, 0.45454545f);
+    }
+
+    /// Convert color from gamma to linear space.
+    Color GammaToLinear() const { return { ConvertGammaToLinear(r_), ConvertGammaToLinear(g_), ConvertGammaToLinear(b_), a_ }; }
+
+    /// Convert color from linear to gamma space.
+    Color LinearToGamma() const { return { ConvertLinearToGamma(r_), ConvertLinearToGamma(g_), ConvertLinearToGamma(b_), a_ }; }
+
     /// Return lightness as defined for HSL: average of the largest and smallest values of the RGB components.
     float Lightness() const;
 
@@ -186,6 +246,9 @@ public:
     /// Return as string.
     String ToString() const;
 
+    /// Return color packed to a 32-bit integer, with B component in the lowest 8 bits. Components are clamped to [0, 1] range.
+    unsigned ToUIntArgb() const { return ToUIntMask(ARGB); }
+
     /// Return hash value for HashSet & HashMap.
     unsigned ToHash() const { return ToUInt(); }
 

+ 29 - 5
Source/Urho3D/Math/MathDefs.h

@@ -112,6 +112,9 @@ inline unsigned FloatToRawIntBits(float value)
 /// Check whether a floating point value is NaN.
 template <class T> inline bool IsNaN(T value) { return std::isnan(value); }
 
+/// Check whether a floating point value is positive or negative infinity
+template <class T> inline bool IsInf(T value) { return std::isinf(value); }
+
 /// Clamp a number to a range.
 template <class T>
 inline T Clamp(T value, T min, T max)
@@ -162,8 +165,21 @@ template <class T> inline T Ln(T x) { return log(x); }
 /// Return square root of X.
 template <class T> inline T Sqrt(T x) { return sqrt(x); }
 
-/// Return floating-point remainder of X/Y.
-template <class T> inline T Mod(T x, T y) { return fmod(x, y); }
+/// Return remainder of X/Y for float values.
+template <class T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+inline T Mod(T x, T y) { return fmod(x, y); }
+
+/// Return remainder of X/Y for integer values.
+template <class T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+inline T Mod(T x, T y) { return x % y; }
+
+/// Return always positive remainder of X/Y.
+template <class T>
+inline T AbsMod(T x, T y)
+{
+    const T result = Mod(x, y);
+    return result < 0 ? result + y : result;
+}
 
 /// Return fractional part of passed value in range [0, 1).
 template <class T> inline T Fract(T value) { return value - floor(value); }
@@ -203,9 +219,9 @@ template <class T> inline T RoundToNearestMultiple(T x, T multiple)
     multiple = Abs(multiple);
     T remainder = Mod(mag, multiple);
     if (remainder >= multiple / 2)
-        return (FloorToInt<T>(mag / multiple) * multiple + multiple)*Sign(x);
+        return (FloorToInt<T>(mag / multiple) * multiple + multiple) * Sign(x);
     else
-        return (FloorToInt<T>(mag / multiple) * multiple)*Sign(x);
+        return (FloorToInt<T>(mag / multiple) * multiple) * Sign(x);
 }
 
 /// Round value up.
@@ -233,6 +249,14 @@ inline unsigned NextPowerOfTwo(unsigned value)
     return ++value;
 }
 
+/// Round up or down to the closest power of two.
+inline unsigned ClosestPowerOfTwo(unsigned value)
+{
+    const unsigned next = NextPowerOfTwo(value);
+    const unsigned prev = next >> 1u;
+    return (value - prev) > (next - value) ? next : prev;
+}
+
 /// Return log base two or the MSB position of the given value.
 inline unsigned LogBaseTwo(unsigned value)
 {
@@ -254,7 +278,7 @@ inline unsigned CountSetBits(unsigned value)
 }
 
 /// Update a hash with the given 8-bit value using the SDBM algorithm.
-inline unsigned SDBMHash(unsigned hash, unsigned char c) { return c + (hash << 6u) + (hash << 16u) - hash; }
+inline constexpr unsigned SDBMHash(unsigned hash, unsigned char c) { return c + (hash << 6u) + (hash << 16u) - hash; }
 
 /// Return a random float between 0.0 (inclusive) and 1.0 (exclusive.)
 inline float Random() { return Rand() / 32768.0f; }

+ 24 - 0
Source/Urho3D/Math/Matrix2.h

@@ -202,6 +202,30 @@ public:
     /// Return float data.
     const float* Data() const { return &m00_; }
 
+    /// Return whether any element is NaN.
+    bool IsNaN() const
+    {
+        const float* data = Data();
+        for (unsigned i = 0; i < 4; ++i)
+        {
+            if (Urho3D::IsNaN(data[i]))
+                return true;
+        }
+        return false;
+    }
+
+    /// Return whether any element is Inf.
+    bool IsInf() const
+    {
+        const float* data = Data();
+        for (unsigned i = 0; i < 4; ++i)
+        {
+            if (Urho3D::IsInf(data[i]))
+                return true;
+        }
+        return false;
+    }
+
     /// Return as string.
     String ToString() const;
 

+ 24 - 0
Source/Urho3D/Math/Matrix3.h

@@ -271,6 +271,30 @@ public:
     /// Return matrix column.
     Vector3 Column(unsigned j) const { return Vector3(Element(0, j), Element(1, j), Element(2, j)); }
 
+    /// Return whether any element is NaN.
+    bool IsNaN() const
+    {
+        const float* data = Data();
+        for (unsigned i = 0; i < 9; ++i)
+        {
+            if (Urho3D::IsNaN(data[i]))
+                return true;
+        }
+        return false;
+    }
+
+    /// Return whether any element is Inf.
+    bool IsInf() const
+    {
+        const float* data = Data();
+        for (unsigned i = 0; i < 9; ++i)
+        {
+            if (Urho3D::IsInf(data[i]))
+                return true;
+        }
+        return false;
+    }
+
     /// Return as string.
     String ToString() const;
 

+ 24 - 0
Source/Urho3D/Math/Matrix3x4.h

@@ -665,6 +665,30 @@ public:
     /// Return matrix column.
     Vector3 Column(unsigned j) const { return Vector3(Element(0, j), Element(1, j), Element(2, j)); }
 
+    /// Return whether any element is NaN.
+    bool IsNaN() const
+    {
+        const float* data = Data();
+        for (unsigned i = 0; i < 12; ++i)
+        {
+            if (Urho3D::IsNaN(data[i]))
+                return true;
+        }
+        return false;
+    }
+
+    /// Return whether any element is Inf.
+    bool IsInf() const
+    {
+        const float* data = Data();
+        for (unsigned i = 0; i < 12; ++i)
+        {
+            if (Urho3D::IsInf(data[i]))
+                return true;
+        }
+        return false;
+    }
+
     /// Return as string.
     String ToString() const;
 

+ 24 - 0
Source/Urho3D/Math/Matrix4.h

@@ -647,6 +647,30 @@ public:
     /// Return matrix column.
     Vector4 Column(unsigned j) const { return Vector4(Element(0, j), Element(1, j), Element(2, j), Element(3, j)); }
 
+    /// Return whether any element is NaN.
+    bool IsNaN() const
+    {
+        const float* data = Data();
+        for (unsigned i = 0; i < 16; ++i)
+        {
+            if (Urho3D::IsNaN(data[i]))
+                return true;
+        }
+        return false;
+    }
+
+    /// Return whether any element is Inf.
+    bool IsInf() const
+    {
+        const float* data = Data();
+        for (unsigned i = 0; i < 16; ++i)
+        {
+            if (Urho3D::IsInf(data[i]))
+                return true;
+        }
+        return false;
+    }
+
     /// Return as string.
     String ToString() const;
 

+ 10 - 1
Source/Urho3D/Math/Quaternion.h

@@ -110,6 +110,12 @@ public:
         FromEulerAngles(x, y, z);
     }
 
+    /// Construct from Euler angles (in degrees.)
+    explicit Quaternion(const Vector3& angles) noexcept
+    {
+        FromEulerAngles(angles.x_, angles.y_, angles.z_);
+    }
+
     /// Construct from the rotation difference between two direction vectors.
     Quaternion(const Vector3& start, const Vector3& end) noexcept
     {
@@ -409,9 +415,12 @@ public:
         return Urho3D::Equals(w_, rhs.w_) && Urho3D::Equals(x_, rhs.x_) && Urho3D::Equals(y_, rhs.y_) && Urho3D::Equals(z_, rhs.z_);
     }
 
-    /// Return whether is NaN.
+    /// Return whether any element is NaN.
     bool IsNaN() const { return Urho3D::IsNaN(w_) || Urho3D::IsNaN(x_) || Urho3D::IsNaN(y_) || Urho3D::IsNaN(z_); }
 
+    /// Return whether any element is Inf.
+    bool IsInf() const { return Urho3D::IsInf(w_) || Urho3D::IsInf(x_) || Urho3D::IsInf(y_) || Urho3D::IsInf(z_); }
+
     /// Return conjugate.
     Quaternion Conjugate() const
     {

+ 1 - 1
Source/Urho3D/Math/Rect.h

@@ -229,7 +229,7 @@ public:
     }
 
     /// Return float data.
-    const void* Data() const { return &min_.x_; }
+    const float* Data() const { return &min_.x_; }
 
     /// Return as a vector.
     Vector4 ToVector4() const { return Vector4(min_.x_, min_.y_, max_.x_, max_.y_); }

+ 33 - 3
Source/Urho3D/Math/Vector2.h

@@ -324,22 +324,46 @@ public:
     /// Test for equality with another vector with epsilon.
     bool Equals(const Vector2& rhs) const { return Urho3D::Equals(x_, rhs.x_) && Urho3D::Equals(y_, rhs.y_); }
 
-    /// Return whether is NaN.
+    /// Return whether any component is NaN.
     bool IsNaN() const { return Urho3D::IsNaN(x_) || Urho3D::IsNaN(y_); }
 
+    /// Return whether any component is Inf.
+    bool IsInf() const { return Urho3D::IsInf(x_) || Urho3D::IsInf(y_); }
+
     /// Return normalized to unit length.
     Vector2 Normalized() const
     {
-        float lenSquared = LengthSquared();
+        const float lenSquared = LengthSquared();
         if (!Urho3D::Equals(lenSquared, 1.0f) && lenSquared > 0.0f)
         {
-            float invLen = 1.0f / sqrtf(lenSquared);
+            const float invLen = 1.0f / sqrtf(lenSquared);
             return *this * invLen;
         }
         else
             return *this;
     }
 
+    /// Return normalized to unit length or zero if length is too small.
+    Vector2 NormalizedOrDefault(const Vector2& defaultValue = Vector2::ZERO, float eps = M_LARGE_EPSILON) const
+    {
+        const float lenSquared = LengthSquared();
+        if (lenSquared < eps * eps)
+            return defaultValue;
+        return *this / sqrtf(lenSquared);
+    }
+
+    /// Return normalized vector with length in given range.
+    Vector2 ReNormalized(float minLength, float maxLength, const Vector2& defaultValue = Vector2::ZERO, float eps = M_LARGE_EPSILON) const
+    {
+        const float lenSquared = LengthSquared();
+        if (lenSquared < eps * eps)
+            return defaultValue;
+
+        const float len = sqrtf(lenSquared);
+        const float newLen = Clamp(len, minLength, maxLength);
+        return *this * (newLen / len);
+    }
+
     /// Return float data.
     const float* Data() const { return &x_; }
 
@@ -389,6 +413,9 @@ inline Vector2 VectorRound(const Vector2& vec) { return Vector2(Round(vec.x_), R
 /// Per-component ceil of 2-vector.
 inline Vector2 VectorCeil(const Vector2& vec) { return Vector2(Ceil(vec.x_), Ceil(vec.y_)); }
 
+/// Per-component absolute value of 2-vector.
+inline Vector2 VectorAbs(const Vector2& vec) { return Vector2(Abs(vec.x_), Abs(vec.y_)); }
+
 /// Per-component floor of 2-vector. Returns IntVector2.
 inline IntVector2 VectorFloorToInt(const Vector2& vec) { return IntVector2(FloorToInt(vec.x_), FloorToInt(vec.y_)); }
 
@@ -404,6 +431,9 @@ inline IntVector2 VectorMin(const IntVector2& lhs, const IntVector2& rhs) { retu
 /// Per-component max of two 2-vectors.
 inline IntVector2 VectorMax(const IntVector2& lhs, const IntVector2& rhs) { return IntVector2(Max(lhs.x_, rhs.x_), Max(lhs.y_, rhs.y_)); }
 
+/// Per-component absolute value of integer 2-vector.
+inline IntVector2 VectorAbs(const IntVector2& vec) { return IntVector2(Abs(vec.x_), Abs(vec.y_)); }
+
 /// Return a random value from [0, 1) from 2-vector seed.
 /// http://stackoverflow.com/questions/12964279/whats-the-origin-of-this-glsl-rand-one-liner
 inline float StableRandom(const Vector2& seed) { return Fract(Sin(seed.DotProduct(Vector2(12.9898f, 78.233f)) * M_RADTODEG) * 43758.5453f); }

+ 32 - 2
Source/Urho3D/Math/Vector3.h

@@ -413,13 +413,16 @@ public:
     /// Returns the angle between this vector and another vector in degrees.
     float Angle(const Vector3& rhs) const { return Urho3D::Acos(DotProduct(rhs) / (Length() * rhs.Length())); }
 
-    /// Return whether is NaN.
+    /// Return whether any component is NaN.
     bool IsNaN() const { return Urho3D::IsNaN(x_) || Urho3D::IsNaN(y_) || Urho3D::IsNaN(z_); }
 
+    /// Return whether any component is Inf.
+    bool IsInf() const { return Urho3D::IsInf(x_) || Urho3D::IsInf(y_) || Urho3D::IsInf(z_); }
+
     /// Return normalized to unit length.
     Vector3 Normalized() const
     {
-        float lenSquared = LengthSquared();
+        const float lenSquared = LengthSquared();
         if (!Urho3D::Equals(lenSquared, 1.0f) && lenSquared > 0.0f)
         {
             float invLen = 1.0f / sqrtf(lenSquared);
@@ -429,6 +432,27 @@ public:
             return *this;
     }
 
+    /// Return normalized to unit length or zero if length is too small.
+    Vector3 NormalizedOrDefault(const Vector3& defaultValue = Vector3::ZERO, float eps = M_LARGE_EPSILON) const
+    {
+        const float lenSquared = LengthSquared();
+        if (lenSquared < eps * eps)
+            return defaultValue;
+        return *this / sqrtf(lenSquared);
+    }
+
+    /// Return normalized vector with length in given range.
+    Vector3 ReNormalized(float minLength, float maxLength, const Vector3& defaultValue = Vector3::ZERO, float eps = M_LARGE_EPSILON) const
+    {
+        const float lenSquared = LengthSquared();
+        if (lenSquared < eps * eps)
+            return defaultValue;
+
+        const float len = sqrtf(lenSquared);
+        const float newLen = Clamp(len, minLength, maxLength);
+        return *this * (newLen / len);
+    }
+
     /// Return float data.
     const float* Data() const { return &x_; }
 
@@ -495,6 +519,9 @@ inline Vector3 VectorRound(const Vector3& vec) { return Vector3(Round(vec.x_), R
 /// Per-component ceil of 3-vector.
 inline Vector3 VectorCeil(const Vector3& vec) { return Vector3(Ceil(vec.x_), Ceil(vec.y_), Ceil(vec.z_)); }
 
+/// Per-component absolute value of 3-vector.
+inline Vector3 VectorAbs(const Vector3& vec) { return Vector3(Abs(vec.x_), Abs(vec.y_), Abs(vec.z_)); }
+
 /// Per-component floor of 3-vector. Returns IntVector3.
 inline IntVector3 VectorFloorToInt(const Vector3& vec) { return IntVector3(FloorToInt(vec.x_), FloorToInt(vec.y_), FloorToInt(vec.z_)); }
 
@@ -510,6 +537,9 @@ inline IntVector3 VectorMin(const IntVector3& lhs, const IntVector3& rhs) { retu
 /// Per-component max of two 3-vectors.
 inline IntVector3 VectorMax(const IntVector3& lhs, const IntVector3& rhs) { return IntVector3(Max(lhs.x_, rhs.x_), Max(lhs.y_, rhs.y_), Max(lhs.z_, rhs.z_)); }
 
+/// Per-component absolute value of integer 3-vector.
+inline IntVector3 VectorAbs(const IntVector3& vec) { return IntVector3(Abs(vec.x_), Abs(vec.y_), Abs(vec.z_)); }
+
 /// Return a random value from [0, 1) from 3-vector seed.
 inline float StableRandom(const Vector3& seed) { return StableRandom(Vector2(StableRandom(Vector2(seed.x_, seed.y_)), seed.z_)); }
 

+ 10 - 1
Source/Urho3D/Math/Vector4.h

@@ -191,9 +191,18 @@ public:
         return Urho3D::Equals(x_, rhs.x_) && Urho3D::Equals(y_, rhs.y_) && Urho3D::Equals(z_, rhs.z_) && Urho3D::Equals(w_, rhs.w_);
     }
 
-    /// Return whether is NaN.
+    /// Return whether any component is NaN.
     bool IsNaN() const { return Urho3D::IsNaN(x_) || Urho3D::IsNaN(y_) || Urho3D::IsNaN(z_) || Urho3D::IsNaN(w_); }
 
+    /// Return whether any component is Inf.
+    bool IsInf() const { return Urho3D::IsInf(x_) || Urho3D::IsInf(y_) || Urho3D::IsInf(z_) || Urho3D::IsInf(w_); }
+
+    /// Convert to Vector2.
+    explicit operator Vector2() const { return { x_, y_ }; }
+
+    /// Convert to Vector3.
+    explicit operator Vector3() const { return { x_, y_, z_ }; }
+
     /// Return float data.
     const float* Data() const { return &x_; }
 

+ 15 - 1
Source/Urho3D/Resource/Image.cpp

@@ -216,7 +216,7 @@ struct DDSurfaceDesc2
     unsigned dwTextureStage_;
 };
 
-bool CompressedLevel::Decompress(unsigned char* dest)
+bool CompressedLevel::Decompress(unsigned char* dest) const
 {
     if (!data_)
         return false;
@@ -2094,6 +2094,20 @@ CompressedLevel Image::GetCompressedLevel(unsigned index) const
     }
 }
 
+SharedPtr<Image> Image::GetDecompressedImage() const
+{
+    if (!IsCompressed())
+        return ConvertToRGBA();
+
+    const CompressedLevel compressedLevel = GetCompressedLevel(0);
+
+    auto decompressedImage = MakeShared<Image>(context_);
+    decompressedImage->SetSize(compressedLevel.width_, compressedLevel.height_, 4);
+    compressedLevel.Decompress(decompressedImage->GetData());
+
+    return decompressedImage;
+}
+
 Image* Image::GetSubimage(const IntRect& rect) const
 {
     if (!data_)

+ 3 - 1
Source/Urho3D/Resource/Image.h

@@ -55,7 +55,7 @@ enum CompressedFormat
 struct CompressedLevel
 {
     /// Decompress to RGBA. The destination buffer required is width * height * 4 bytes. Return true if successful.
-    bool Decompress(unsigned char* dest);
+    bool Decompress(unsigned char* dest) const;
 
     /// Compressed image data.
     unsigned char* data_{};
@@ -187,6 +187,8 @@ public:
     SharedPtr<Image> ConvertToRGBA() const;
     /// Return a compressed mip level.
     CompressedLevel GetCompressedLevel(unsigned index) const;
+    /// Return decompressed image data in RGBA format.
+    SharedPtr<Image> GetDecompressedImage() const;
     /// Return subimage from the image by the defined rect or null if failed. 3D images are not supported. You must free the subimage yourself.
     Image* GetSubimage(const IntRect& rect) const;
     /// Return an SDL surface from the image, or null if failed. Only RGB images are supported. Specify rect to only return partial image. You must free the surface yourself.