Browse Source

Implemented spatial parent-child hierarchy for all entities, in SpatialComponent, SpatialDataManager, WorldScene
Added ImSpinner dependency, used for loading animations
Added a unified way of checking if any system scene is currently loading
Added a way to change the texture wrap mode for individual meshes, in CommandBuffer, CommonDefinitions, EditorWindow, GraphicsDataSets, ModelComponent, RendererBackend, RendererFrontend, RendererScene, SceneLoader
Added a loading animation during scene loading, in GUIScene
Added a way to toggle rendering during scene loading, in GeometryPass, AtmScatteringPass,
Added debug options for ImGui and ImSpinner in EditorWindow
Added a loading spinner for ModelComponent textures in EditorWindow
Added world transform data for SpatialComponent in EditorWindow
Added a version file complying with Semantic Versioning 2.0.0
Added current version to the window title, in Window
Added more error strings
Added more Config variables
Added more Property IDs
Fixed a bug of math define names colliding with dependencies, by converting the defines into constexpr variables and putting them in a namespace, in Math
Changed how the editor notifies a SpatialComponent of changes, from sending individual vectors to recalculating a local matrix and sending it instead, in EditorWindow
Changed how position and rotation manipulation guizmo calculates the changed transform matrix to accommodate the parent-child hierarchy, in EditorWindow

Paul A 2 years ago
parent
commit
3a7dce6007

+ 1 - 0
.gitignore

@@ -42,3 +42,4 @@ compileData.scor.gbl
 compileData.scor.incl
 compileData.scor.incl
 compileData.scor.t0000
 compileData.scor.t0000
 *.glb
 *.glb
+*.ilk

+ 4462 - 0
Dependencies/include/imspinner.h

@@ -0,0 +1,4462 @@
+#ifndef _IMSPINNER_H_
+#define _IMSPINNER_H_
+
+/*
+ * The MIT License (MIT)
+ * 
+ * Copyright (c) 2021-2022 Dalerank
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * 
+ */
+
+#include <functional>
+#include <array>
+#include <vector>
+#include <cmath>
+#include <map>
+#include <cctype>
+
+#ifdef __has_include
+    #if !__has_include(<imgui.h>)
+        #error "Couldn't find imgui.h in the header include path, please add it to the path!"
+    #endif // !<imgui.h>
+#endif // __has_include
+
+// imgui headers
+#include "imgui.h"
+#include "imgui_internal.h"
+
+namespace ImSpinner
+{
+    static const ImColor white{1.f, 1.f, 1.f, 1.f};
+    static const ImColor half_white{1.f, 1.f, 1.f, 0.5f};
+    static const ImColor red{1.f,0.f,0.f,1.f};
+
+#define DECLPROP(name, type, def) \
+    struct name { \
+      type value = def; \
+      operator type() { return value; } \
+      name(const type& v) : value(v) {} \
+    };
+
+    enum SpinnerTypeT {
+      e_st_rainbow = 0,
+      e_st_angle,
+      e_st_dots,
+      e_st_ang,
+      e_st_vdots,
+      e_st_bounce_ball,
+      e_st_eclipse,
+      e_st_ingyang,
+
+      e_st_count
+    };
+
+    using float_ptr = float *;
+    constexpr float PI_DIV_4 = IM_PI / 4.f;
+    constexpr float PI_DIV_2 = IM_PI / 2.f;
+    constexpr float PI_2 = IM_PI * 2.f;
+    template<class T> constexpr float PI_DIV(T d) { return IM_PI / (float)d; }
+    template<class T> constexpr float PI_2_DIV(T d) { return PI_2 / (float)d; }
+
+    DECLPROP (SpinnerType, SpinnerTypeT, e_st_rainbow)
+    DECLPROP (Radius, float, 16.f)
+    DECLPROP (Speed, float, 1.f)
+    DECLPROP (Thickness, float, 1.f)
+    DECLPROP (Color, ImColor, white)
+    DECLPROP (BgColor, ImColor, white)
+    DECLPROP (AltColor, ImColor, white)
+    DECLPROP (Angle, float, IM_PI)
+    DECLPROP (AngleMin, float, IM_PI)
+    DECLPROP (AngleMax, float, IM_PI)
+    DECLPROP (FloatPtr, float_ptr, nullptr)
+    DECLPROP (Dots, int, 0)
+    DECLPROP (MiddleDots, int, 0)
+    DECLPROP (MinThickness, float, 0.f)
+    DECLPROP (Reverse, bool, false)
+    DECLPROP (Delta, float, 0.f)
+#undef DECLPROP
+
+    namespace detail {
+      // SpinnerBegin is a function that starts a spinner widget, used to display an animation indicating that
+      // a task is in progress. It returns true if the widget is visible and can be used, or false if it should be skipped.
+      inline bool SpinnerBegin(const char *label, float radius, ImVec2 &pos, ImVec2 &size, ImVec2 &centre, int &num_segments) {
+        ImGuiWindow *window = ImGui::GetCurrentWindow();
+        if (window->SkipItems)
+          return false;
+
+        ImGuiContext &g = *GImGui;
+        const ImGuiStyle &style = g.Style;
+        const ImGuiID id = window->GetID(label);
+
+        pos = window->DC.CursorPos;
+        // The size of the spinner is set to twice the radius, plus some padding based on the style
+        size = ImVec2((radius) * 2, (radius + style.FramePadding.y) * 2);
+
+        const ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
+        ImGui::ItemSize(bb, style.FramePadding.y);
+
+        num_segments = window->DrawList->_CalcCircleAutoSegmentCount(radius);
+
+        centre = bb.GetCenter();
+        // If the item cannot be added to the window, return false
+        if (!ImGui::ItemAdd(bb, id))
+          return false;
+
+        return true;
+      }
+
+#define IMPLRPOP(basetype,type) basetype m_##type; \
+                                void set##type(const basetype& v) { m_##type = v;} \
+                                void set(type h) { m_##type = h.value;} \
+                                template<typename First, typename... Args> \
+                                void set(const type& h, const Args&... args) { set##type(h.value); this->template set<Args...>(args...); }
+      struct SpinnerConfig {
+        SpinnerConfig() {}
+        template<typename none = void> void set() {}
+
+        template<typename... Args>
+        SpinnerConfig(const Args&... args) { this->template set<Args...>(args...); }
+
+        IMPLRPOP(SpinnerTypeT, SpinnerType)
+        IMPLRPOP(float, Radius)
+        IMPLRPOP(float, Speed)
+        IMPLRPOP(float, Thickness)
+        IMPLRPOP(ImColor, Color)
+        IMPLRPOP(ImColor, BgColor)
+        IMPLRPOP(ImColor, AltColor)
+        IMPLRPOP(float, Angle)
+        IMPLRPOP(float, AngleMin)
+        IMPLRPOP(float, AngleMax)
+        IMPLRPOP(float_ptr, FloatPtr)
+        IMPLRPOP(int, Dots)
+        IMPLRPOP(int, MiddleDots)
+        IMPLRPOP(float, MinThickness)
+        IMPLRPOP(bool, Reverse)
+        IMPLRPOP(float, Delta)
+      };
+#undef IMPLRPOP
+    }
+
+#define SPINNER_HEADER(pos, size, centre, num_segments) \
+  ImVec2 pos, size, centre; int num_segments; \
+  if (!detail::SpinnerBegin(label, radius, pos, size, centre, num_segments)) { return; }; \
+  ImGuiWindow *window = ImGui::GetCurrentWindow(); \
+  auto circle = [&] (const std::function<ImVec2 (int)>& point_func, ImU32 dbc, float dth) { \
+    window->DrawList->PathClear(); \
+    for (int i = 0; i < num_segments; i++) { \
+      ImVec2 p = point_func(i); \
+      window->DrawList->PathLineTo(ImVec2(centre.x + p.x, centre.y + p.y)); \
+    } \
+    window->DrawList->PathStroke(dbc, 0, dth); \
+  }
+    
+    inline ImColor color_alpha(ImColor c, float alpha) { c.Value.w *= alpha * ImGui::GetStyle().Alpha; return c; }
+
+    inline float damped_spring (double mass, double stiffness, double damping, double time, float a = PI_DIV_2, float b = PI_DIV_2) {
+        double omega = sqrt(stiffness / mass);
+        double alpha = damping / (2.0 * mass);
+        double exponent = exp(-alpha * time);
+        double cosTerm = cos(omega * sqrt(1 - alpha * alpha) * time);
+        float result = (float)(exponent * cosTerm);
+        return ((result *= a) + b);
+    };
+
+    inline float damped_gravity(float limtime) {
+        float time = 0.0f, initialHeight = 10.f, height = initialHeight, velocity = 0.0f, prtime = 0.0f;
+
+        while (height >= 0.0f) {
+            if (prtime >= limtime) { return height / 10.f; }
+            time += 0.01f; prtime += 0.01f;
+            height = initialHeight - 0.5f * 9.81f * time * time;
+            if (height < 0.0f) { initialHeight = 0.0f; time = 0.0f; }
+        }
+        return 0.f;
+    }
+    
+    /*
+        const char *label: A string label for the spinner, used to identify it in ImGui.
+        float radius: The radius of the spinner.
+        float thickness: The thickness of the spinner's border.
+        const ImColor &color: The color of the spinner.
+        float speed: The speed of the spinning animation.
+        float ang_min: Minimum angle of spinning.
+        float ang_max: Maximum angle of spinning.
+        int arcs: Number of arcs of the spinner.
+    */
+    inline void SpinnerRainbow(const char *label, float radius, float thickness, const ImColor &color, float speed, float ang_min = 0.f, float ang_max = PI_2, int arcs = 1)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        for (int i = 0; i < arcs; ++i)
+        {
+            const float rb = (radius / arcs) * (i + 1);
+
+            const float start = ImAbs(ImSin((float)ImGui::GetTime()) * (num_segments - 5));
+            const float a_min = ImMax(ang_min, (float)PI_2 * ((float)start) / (float)num_segments + (IM_PI / arcs) * i);
+            const float a_max = ImMin(ang_max, (float)PI_2 * ((float)num_segments + 3 * (i + 1)) / (float)num_segments);
+
+            circle([&] (int i) {
+                const float a =  a_min + ((float)i / (float)num_segments) * (a_max - a_min);
+                const float rspeed = a + (float)ImGui::GetTime() * speed;
+                return ImVec2(ImCos(rspeed) * rb, ImSin(rspeed) * rb);
+            }, color_alpha(color, 1.f), thickness);
+        }
+    }
+
+    inline void SpinnerRainbowMix(const char *label, float radius, float thickness, const ImColor &color, float speed, float ang_min = 0.f, float ang_max = PI_2, int arcs = 1, int mode = 0)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (int i = 0; i < arcs; ++i)
+        {
+            const float rb = (radius / arcs) * (i + 1);
+
+            const float start = ImAbs(ImSin((float)ImGui::GetTime()) * (num_segments - 5));
+            const float a_min = ImMax(ang_min, (float)PI_2 * ((float)start) / (float)num_segments + (IM_PI / arcs) * i);
+            const float a_max = ImMin(ang_max, (float)PI_2 * ((float)num_segments + 3 * (i + 1)) / (float)num_segments);
+            const float koeff = mode ? (1.1f - 1.f / (i+1)) : 1.f;
+            ImColor c = ImColor::HSV(out_h + i * (1.f / arcs), out_s, out_v);
+
+            circle([&] (int i) {
+                const float a =  a_min + ((float)i / (float)num_segments) * (a_max - a_min);
+                const float rspeed = a + (float)ImGui::GetTime() * speed * koeff;
+                return ImVec2(ImCos(rspeed) * rb, ImSin(rspeed) * rb);
+            }, color_alpha(c, 1.f), thickness);
+        }
+    }
+
+    // This function draws a rotating heart spinner. 
+    inline void SpinnerRotatingHeart(const char *label, float radius, float thickness, const ImColor &color, float speed, float ang_min = 0.f)
+    {
+        // Calculate the position and size of the spinner, as well as the number of segments it will be divided into.
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        // Calculate the start angle of the spinner based on the current time and speed.
+        const float start = (float)ImGui::GetTime() * speed;
+
+        // Modify the number of segments to ensure the heart shape is complete.
+        num_segments = (num_segments * 3) / 2;
+
+        // Create a lambda function to rotate points.
+        auto rotate = [] (const ImVec2 &point, float angle) {
+            const float s = ImSin(angle), c = ImCos(angle);
+            return ImVec2(point.x * c - point.y * s, point.x * s + point.y * c);
+        };
+
+        // Calculate the radius of the bottom of the heart.
+        const float rb = radius * ImMax(0.8f, ImSin(start * 2));
+        auto scale = [rb] (float v) { return v / 16.f * rb; };
+
+        // Draw the heart spinner by calling the circle function, passing in a lambda function that defines the shape of the heart.
+        circle([&] (int i) {
+            const float a = PI_2 * i / num_segments;
+            const float x = (scale(16) * ImPow(ImSin(a), 3));
+            const float y = -1.f * (scale(13) * ImCos(a) - scale(5) * ImCos(2 * a) - scale(2) * ImCos(3 * a) - ImCos(4 * a));
+            return rotate(ImVec2(x, y), ang_min);
+        }, color_alpha(color, 1.f), thickness); 
+    }
+
+    // SpinnerAng is a function that draws a spinner widget with a given angle.
+    inline void SpinnerAng(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bg = white, float speed = 2.8f, float angle = IM_PI, int mode = 0)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);                            // Get the position, size, centre, and number of segments of the spinner using the SPINNER_HEADER macro.
+        float start = (float)ImGui::GetTime() * speed;                        // The start angle of the spinner is calculated based on the current time and the specified speed.
+        radius = (mode == 2) ? (0.8f + ImCos(start) * 0.2f) * radius : radius;
+
+        circle([&] (int i) {                                                         // Draw the background of the spinner using the `circle` function, with the specified background color and thickness.
+            const float a = start + (i * (PI_2 / (num_segments - 1)));               // Calculate the angle for each segment based on the start angle and the number of segments.
+            return ImVec2(ImCos(a) * radius, ImSin(a) * radius);
+        }, color_alpha(bg, 1.f), thickness);
+
+        const float b = (mode == 1) ? damped_gravity(ImSin(start * 1.1f)) * angle : 0.f;
+        circle([&] (int i) {                                                        // Draw the spinner itself using the `circle` function, with the specified color and thickness.
+            const float a = start - b + (i * angle / num_segments);
+            return ImVec2(ImCos(a) * radius, ImSin(a) * radius);
+        }, color_alpha(color, 1.f), thickness);
+    }
+
+    inline void SpinnerAngMix(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, float angle = IM_PI, int arcs = 4, int mode = 0)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);                            // Get the position, size, centre, and number of segments of the spinner using the SPINNER_HEADER macro.
+
+        for (int i = 0; i < arcs; ++i)
+        {
+            const float koeff = (1.1f - 1.f / (i+1));
+            float start = (float)ImGui::GetTime() * speed * koeff;                        // The start angle of the spinner is calculated based on the current time and the specified speed.
+            radius = (mode == 2) ? (0.8f + ImCos(start) * 0.2f) * radius : radius;
+            const float rb = (radius / arcs) * (i + 1);
+            const float b = (mode == 1) ? damped_gravity(ImSin(start * 1.1f)) * angle : 0.f;
+            circle([&] (int i) {                                                        // Draw the spinner itself using the `circle` function, with the specified color and thickness.
+                const float a = start - b + (i * angle / num_segments);
+                return ImVec2(ImCos(a) * rb, ImSin(a) * rb);
+            }, color_alpha(color, 1.f), thickness);
+        }
+    }
+
+    inline void SpinnerLoadingRing(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bg = half_white, float speed = 2.8f, int segments = 5)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, IM_PI);                         // Calculate the starting angle based on the current time and speed
+        const float bg_angle_offset = PI_2 / num_segments - 1;
+
+        num_segments *= 2;                                                                          // Double the number of segments for the background ringxxxxxxx
+        circle([&] (int i) { 
+            return ImVec2(ImCos(i * bg_angle_offset) * radius, ImSin(i * bg_angle_offset) * radius); // Draw the background ring
+        }, color_alpha(bg, 1.f), thickness);
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v); // Convert the color to HSV for variation in segment colors
+    
+        const float start_ang = (start < PI_DIV_2) ? 0.f : (start - PI_DIV_2) * 4.f;                // Calculate the angles and delta angle for each segment
+        const float angle_offset = ((start < PI_DIV_2) ? PI_2 : (PI_2 - start_ang)) / segments;
+        const float delta_angle = (start < PI_DIV_2) ? ImSin(start) * angle_offset : angle_offset;
+        for (int i = 0; i < segments; ++i)                                                          // Draw each segment of the loading ring
+        {
+            window->DrawList->PathClear();
+            const float begin_ang = start_ang - PI_DIV_2 + delta_angle * i;
+            ImColor c = ImColor::HSV(out_h + i * (1.f / segments * 2.f), out_s, out_v);
+            window->DrawList->PathArcTo(centre, radius, begin_ang, begin_ang + delta_angle, num_segments);
+            window->DrawList->PathStroke(color_alpha(c, 1.f), false, thickness);
+        }
+    }
+
+    inline void SpinnerClock(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bg = half_white, float speed = 2.8f)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime() * speed;
+      const float bg_angle_offset = PI_2 / (num_segments - 1);
+      
+      circle([&] (int i) { return ImVec2(ImCos(i * bg_angle_offset) * radius, ImSin(i * bg_angle_offset) * radius); }, color_alpha(bg, 1.f), thickness);
+
+      window->DrawList->AddLine(centre, ImVec2(centre.x + ImCos(start) * radius, centre.y + ImSin(start) * radius), color_alpha(color, 1.f), thickness * 2);
+      window->DrawList->AddLine(centre, ImVec2(centre.x + ImCos(start * 0.5f) * radius / 2.f, centre.y + ImSin(start * 0.5f) * radius / 2.f), color_alpha(color, 1.f), thickness * 2);
+    }
+
+    inline void SpinnerPulsar(const char *label, float radius, float thickness, const ImColor &bg = half_white, float speed = 2.8f, bool sequence = true)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      ImGuiStorage* storage = window->DC.StateStorage;
+      const ImGuiID radiusbId = window->GetID("##radiusb");
+      float radius_b = storage->GetFloat(radiusbId, 0.8f);
+
+      const float start = (float)ImGui::GetTime() * speed;
+      const float bg_angle_offset = PI_2 / (num_segments - 1);
+
+      float start_r = ImFmod(start, PI_DIV_2);
+      float radius_k = ImSin(start_r);
+      float radius1 = radius_k * radius;
+
+      circle([&] (int i) {
+          return ImVec2(ImCos(i * bg_angle_offset) * radius1, ImSin(i * bg_angle_offset) * radius1);
+      }, color_alpha(bg, 1.f), thickness);
+
+      if (sequence) { radius_b -= (0.005f * speed); radius_b = ImMax(radius_k, ImMax(0.8f, radius_b)); } 
+      else { radius_b = (1.f - radius_k); }
+      storage->SetFloat(radiusbId, radius_b);
+      
+      float radius_tb = sequence ? ImMax(radius_k, radius_b) * radius : (radius_b * radius);
+      circle([&] (int i) {
+          return ImVec2(ImCos(i * bg_angle_offset) * radius_tb, ImSin(i * bg_angle_offset) * radius_tb);
+      }, color_alpha(bg, 1.f), thickness);
+    }
+
+    inline void SpinnerDoubleFadePulsar(const char *label, float radius, float /*thickness*/, const ImColor &bg = half_white, float speed = 2.8f)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      ImGuiStorage* storage = window->DC.StateStorage;
+      const ImGuiID radiusbId = window->GetID("##radiusb");
+      float radius_b = storage->GetFloat(radiusbId, 0.8f);
+
+      const float start = (float)ImGui::GetTime() * speed;
+      const float bg_angle_offset = PI_2_DIV(num_segments);
+
+      float start_r = ImFmod(start, PI_DIV_2);
+      float radius_k = ImSin(start_r);
+      window->DrawList->AddCircleFilled(centre, radius_k * radius, color_alpha(bg, ImMin(0.1f, radius_k)), num_segments);
+
+      radius_b = (1.f - radius_k);
+      storage->SetFloat(radiusbId, radius_b);
+
+      window->DrawList->AddCircleFilled(centre, radius_b * radius, color_alpha(bg, ImMin(0.3f, radius_b)), num_segments);
+    }
+
+    inline void SpinnerTwinPulsar(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int rings = 2)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float bg_angle_offset = PI_2 / (num_segments - 1);
+      const float koeff = PI_DIV(2 * rings);
+      float start = (float)ImGui::GetTime() * speed;
+
+      for (int num_ring = 0; num_ring < rings; ++num_ring) {
+        float radius_k = ImSin(ImFmod(start + (num_ring * koeff), PI_DIV_2));
+        float radius1 = radius_k * radius;
+
+        circle([&] (int i) {
+            const float a = start + (i * bg_angle_offset);
+            return ImVec2(ImCos(a) * radius1, ImSin(a) * radius1);
+        }, color_alpha(color, radius_k > 0.5f ? 2.f - (radius_k * 2.f) : color.Value.w), thickness);
+      }
+    }
+
+    inline void SpinnerFadePulsar(const char *label, float radius, const ImColor &color = white, float speed = 2.8f, int rings = 2)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float bg_angle_offset = PI_2_DIV(num_segments);
+      const float koeff = PI_DIV(2 * rings);
+      float start = (float)ImGui::GetTime() * speed;
+
+      for (int num_ring = 0; num_ring < rings; ++num_ring) {
+        float radius_k = ImSin(ImFmod(start + (num_ring * koeff), PI_DIV_2));
+        ImColor c = color_alpha(color, (radius_k > 0.5f) ? (2.f - (radius_k * 2.f)) : color.Value.w);
+        window->DrawList->AddCircleFilled(centre, radius_k * radius, c, num_segments);
+      }
+    }
+
+    inline void SpinnerCircularLines(const char *label, float radius, const ImColor &color = white, float speed = 1.8f, int lines = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        auto ghalf_pi = [] (float f) -> float { return ImMin(f, PI_DIV_2); };
+        const float start = ImFmod((float)ImGui::GetTime() * speed, IM_PI);
+        const float bg_angle_offset = PI_2_DIV(lines);
+        for (size_t j = 0; j < 3; ++j)
+        {
+            const float start_offset = j * PI_DIV(7.f);
+            const float rmax = ImMax(ImSin(ghalf_pi(start - start_offset)), 0.3f) * radius;
+            const float rmin = ImMax(ImSin(ghalf_pi(start - PI_DIV_4 - start_offset)), 0.3f) * radius;
+
+            ImColor c = color_alpha(color, 1.f - j * 0.3f);
+            for (size_t i = 0; i <= lines; i++)
+            {
+                float a = (i * bg_angle_offset);
+                window->DrawList->AddLine(ImVec2(centre.x + ImCos(a) * rmin, centre.y + ImSin(a) * rmin),
+                                          ImVec2(centre.x + ImCos(a) * rmax, centre.y + ImSin(a) * rmax),
+                                          color_alpha(c, 1.f), 1.f);
+            }
+        }
+    }
+
+    inline void SpinnerDots(const char *label, float *nextdot, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t dots = 12, float minth = -1.f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        const float bg_angle_offset = PI_2 / dots;
+        dots = ImMin(dots, (size_t)32);
+        const size_t mdots = dots / 2;
+
+        float def_nextdot = 0;
+        float &ref_nextdot = nextdot ? *nextdot : def_nextdot;
+        if (ref_nextdot < 0.f)
+          ref_nextdot = (float)dots;
+
+        auto thcorrect = [&thickness, &ref_nextdot, &mdots, &minth] (size_t i) {
+            const float nth = minth < 0.f ? thickness / 2.f : minth;
+            return ImMax(nth, ImSin(((i - ref_nextdot) / mdots) * IM_PI) * thickness);
+        };
+
+        for (size_t i = 0; i <= dots; i++)
+        {
+            float a = start + (i * bg_angle_offset);
+            a = ImFmod(a, PI_2);
+            float th = minth < 0 ? thickness / 2.f : minth;
+
+            if (ref_nextdot + mdots < dots) {
+                if (i > ref_nextdot && i < ref_nextdot + mdots)
+                    th = thcorrect(i);
+            } else {
+                if ((i > ref_nextdot && i < dots) || (i < ((int)(ref_nextdot + mdots)) % dots))
+                    th = thcorrect(i);
+            }
+
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(-a) * radius, centre.y + ImSin(-a) * radius), th, color_alpha(color, 1.f), 8);
+        }
+    }
+
+    inline void SpinnerVDots(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bgcolor = white, float speed = 2.8f, size_t dots = 12, size_t mdots = 6)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        const float bg_angle_offset = PI_2_DIV(dots);
+        dots = ImMin(dots, (size_t)32);
+
+        for (size_t i = 0; i <= dots; i++)
+        {
+            float a = ImFmod(start + (i * bg_angle_offset), PI_2);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(-a) * radius, centre.y + ImSin(-a) * radius), thickness / 2, color_alpha(bgcolor, 1.f), 8);
+        }
+
+        window->DrawList->PathClear();
+        const float d_ang = (mdots / (float)dots) * PI_2;
+        const float angle_offset = (d_ang) / dots;
+        for (size_t i = 0; i < dots; i++)
+        {
+            const float a = start + (i * angle_offset);
+            window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius));
+        }
+        window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+    }
+
+    inline void SpinnerBounceDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t dots = 3, int mode = 0)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float nextItemKoeff = 2.5f;
+      const float heightKoeff = 2.f;
+      const float heightSpeed = 0.8f;
+      const float hsize = dots * (thickness * nextItemKoeff) / 2.f - (thickness * nextItemKoeff) * 0.5f;
+
+      float start = (float)ImGui::GetTime() * speed;
+
+      const float offset = PI_DIV(dots);
+      for (size_t i = 0; i < dots; i++) {
+        float a = mode ? damped_spring(1, 10.f, 1.0f, ImSin(ImFmod(start + i * PI_DIV(dots*2), PI_2))) : start + (IM_PI - i * offset);
+        float y =  centre.y + ImSin(a * heightSpeed) * thickness * heightKoeff;
+        window->DrawList->AddCircleFilled(ImVec2(centre.x - hsize + i * (thickness * nextItemKoeff), ImMin(y, centre.y)), thickness, color_alpha(color, 1.f), 8);
+      }
+    }
+
+    inline void SpinnerZipDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t dots = 5)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 3.5f;
+        const float heightKoeff = 2.f;
+        const float heightSpeed = 0.8f;
+        const float hsize = dots * (thickness * nextItemKoeff) / 2.f - (thickness * nextItemKoeff) * 0.5f;
+        const float start = (float)ImGui::GetTime() * speed;
+        const float offset = PI_DIV(dots);
+
+        for (size_t i = 0; i < dots; i++)
+        {
+            const float sina = ImSin((start + (IM_PI - i * offset)) * heightSpeed);
+            const float y = ImMin(centre.y + sina * thickness * heightKoeff, centre.y);
+            const float deltay = ImAbs(y - centre.y);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x - hsize + i * (thickness * nextItemKoeff), y), thickness, color_alpha(color, 1.f), 8);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x - hsize + i * (thickness * nextItemKoeff), y + 2 * deltay), thickness, color_alpha(color, 1.f), 8);
+        }
+    }
+
+    inline void SpinnerDotsToPoints(const char *label, float radius, float thickness, float offset_k, const ImColor &color = white, float speed = 1.8f, size_t dots = 5)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 3.5f;
+        const float hsize = dots * (thickness * nextItemKoeff) / 2.f - (thickness * nextItemKoeff) * 0.5f;
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float offset = PI_DIV(dots);
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        if (start < PI_DIV_2) {
+            const float sina = ImSin(start);
+            for (size_t i = 0; i < dots; i++) {
+                const float xx = ImMax(sina * (i * (thickness * nextItemKoeff)), 0.f);
+                ImColor c = color_alpha(ImColor::HSV(out_h + i * ((1.f / dots) * 2.f), out_s, out_v), 1.f);
+                window->DrawList->AddCircleFilled(ImVec2(centre.x - hsize + xx, centre.y), thickness, c, 8);
+            }
+        } else {
+            for (size_t i = 0; i < dots; i++) {
+                const float sina = ImSin(ImMax(start - (IM_PI / dots) * i, PI_DIV_2));
+                const float xx = ImMax(1.f * (i * (thickness * nextItemKoeff)), 0.f);
+                const float th = sina * thickness;
+                ImColor c = color_alpha(ImColor::HSV(out_h + i * ((1.f / dots) * 2.f), out_s, out_v), 1.f);
+                window->DrawList->AddCircleFilled(ImVec2(centre.x - hsize + xx, centre.y), th, c, 8);
+            }
+        }
+    }
+    //const float sina = ImSin( ImFmod((start + (IM_PI - i * offset)), PI_DIV_2));
+
+    inline void SpinnerDotsToBar(const char *label, float radius, float thickness, float offset_k, const ImColor &color = white, float speed = 2.8f, size_t dots = 5)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 3.5f;
+        const float heightSpeed = 0.8f;
+        const float hsize = dots * (thickness * nextItemKoeff) / 2.f - (thickness * nextItemKoeff) * 0.5f;
+        const float start = (float)ImGui::GetTime() * speed;
+        const float offset = PI_DIV(dots);
+        const float hradius = (radius);
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (size_t i = 0; i < dots; i++)
+        {
+            const float sina = ImSin((start + (IM_PI - i * offset)) * heightSpeed);
+            const float sinb = ImSin((start + (IM_PI + IM_PI * offset_k - i * offset)) * heightSpeed);
+            const float y = ImMin(centre.y + sina * hradius, centre.y);
+            const float y2 = ImMin(sinb, 0.f) * (hradius * offset_k);
+            const float y3 = (y + y2);
+            const float deltay = ImAbs(y - centre.y);
+            ImColor c = color_alpha(ImColor::HSV(out_h + i * ((1.f / dots) * 2.f), out_s, out_v), 1.f);
+            ImVec2 p1(centre.x - hsize + i * (thickness * nextItemKoeff), y3);
+            ImVec2 p2(centre.x - hsize + i * (thickness * nextItemKoeff), y3 + 2 * deltay);
+            window->DrawList->AddCircleFilled(p1, thickness, c, 8);
+            window->DrawList->AddCircleFilled(p2, thickness, c, 8);
+            window->DrawList->AddLine(p1, p2, c, thickness * 2.f);
+        }
+    }
+
+    inline void SpinnerWaveDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int lt = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 2.5f;
+        const float dots = (size.x / (thickness * nextItemKoeff));
+        const float offset = PI_DIV(dots);
+        const float start = (float)ImGui::GetTime() * speed;
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (size_t i = 0; i < dots; i++)
+        {
+            float a = start + (IM_PI - i * offset);
+            float y = centre.y + ImSin(a) * (size.y / 2.f);
+            ImColor c = ImColor::HSV(out_h + i * (1.f / dots * 2.f), out_s, out_v);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x - (size.x / 2.f) + i * thickness * nextItemKoeff, y), thickness, color_alpha(c, 1.f), lt);
+        }
+    }
+
+    inline void SpinnerFadeDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int lt = 8, int mode = 0)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        const float nextItemKoeff = 2.5f;
+        const float dots = (size.x / (thickness * nextItemKoeff));
+        const float heightSpeed = 0.8f;
+
+        for (size_t i = 0; i < dots; i++)
+        {
+          float a = mode 
+                        ? damped_spring(1, 10.f, 1.0f, ImSin(ImFmod(start + (IM_PI - i * (IM_PI / dots)), PI_2)))
+                        : ImSin(start + (IM_PI - i * (IM_PI / dots)) * heightSpeed);
+          window->DrawList->AddCircleFilled(ImVec2(centre.x - (size.x / 2.f) + i * thickness * nextItemKoeff, centre.y), thickness, color_alpha(color, ImMax(0.1f, a)), lt);
+        }
+    }
+
+    inline void SpinnerThreeDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int lt = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float nextItemKoeff = 2.5f;
+        const float offset = size.x / 4.f;
+
+        float ab = start;
+        int msize = 2;
+        if (start < IM_PI) { ab = 0; msize = 1; }
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = ab + i * IM_PI - PI_DIV_2;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x - offset + ImSin(a) * offset, centre.y + ImCos(a) * offset), thickness, color_alpha(color, 1.f), lt);
+        }
+
+        float ba = start; msize = 2;
+        if (start > IM_PI && start < PI_2) { ba = 0; msize = 1; }
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = -ba + i * IM_PI + PI_DIV_2;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + offset + ImSin(a) * offset, centre.y + ImCos(a) * offset), thickness, color_alpha(color, 1.f), lt);
+        }
+    }
+
+    inline void SpinnerFiveDots(const char *label, float radius, float thickness, const ImColor &color = 0xffffffff, float speed = 2.8f, int lt = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2 * 2);
+        const float nextItemKoeff = 2.5f;
+        const float offset = size.x / 4.f;
+
+        float ab = 0;
+        int msize = 1;
+        if (start < IM_PI) { ab = start; msize = 2; }
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = -ab + i * IM_PI - PI_DIV_2;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x - offset + ImSin(a) * offset, centre.y + ImCos(a) * offset), thickness, color_alpha(color, 1.f), lt);
+        }
+
+        float ba = 0; msize = 1;
+        if (start > IM_PI && start < PI_2) { ba = start; msize = 2; }
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = -ba + i * IM_PI;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(a) * offset, centre.y + offset + ImCos(a) * offset), thickness, color_alpha(color, 1.f), lt);
+        }
+
+        float bc = 0; msize = 1;
+        if (start > PI_2 && start < IM_PI * 3) { bc = start; msize = 2; }
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = -bc + i * IM_PI - IM_PI;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(a) * offset, centre.y - offset + ImCos(a) * offset), thickness, color_alpha(color, 1.f), lt);
+        }
+
+        float bd = 0; msize = 1;
+        if (start > IM_PI * 3 && start < IM_PI * 4) { bd = start; msize = 2; }
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = -bd + i * IM_PI + PI_DIV_2;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + offset + ImSin(a) * offset, centre.y + ImCos(a) * offset), thickness, color_alpha(color, 1.f), lt);
+        }
+    }
+
+    inline void Spinner4Caleidospcope(const char *label, float radius, float thickness, const ImColor &color = 0xffffffff, float speed = 2.8f, int lt = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float nextItemKoeff = 2.5f;
+        const float offset = size.x / 4.f;
+
+        float ab = start;
+        int msize = 2;
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = ab - i * IM_PI;
+            ImColor c = color_alpha(ImColor::HSV(out_h + (0.1f * i), out_s, out_v, 0.7f), 1.f);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x - offset + ImSin(a) * offset, centre.y + ImCos(a) * offset), thickness, c, lt);
+        }
+
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = ab + i * IM_PI + PI_DIV_2;
+            ImColor c = color_alpha(ImColor::HSV(out_h + 0.2f + (0.1f * i), out_s, out_v, 0.7f), 1.f);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(a) * offset, centre.y - offset + ImCos(a) * offset), thickness, c, lt);
+        }
+
+        float ba = start; msize = 2;
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = -ba + i * IM_PI + PI_DIV_2;
+            ImColor c = color_alpha(ImColor::HSV(out_h + 0.4f + (0.1f * i), out_s, out_v, 0.7f), 1.f);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + offset + ImSin(a) * offset, centre.y + ImCos(a) * offset), thickness, c, lt);
+        }
+
+        for (size_t i = 0; i < msize; i++)
+        {
+            float a = ab - i * IM_PI + PI_DIV_4;
+            ImColor c = color_alpha(ImColor::HSV(out_h + 0.6f + (0.1f * i), out_s, out_v, 0.7f), 1.f);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(a) * offset, centre.y + offset + ImCos(a) * offset), thickness, c, lt);
+        }
+    }
+
+    inline void SpinnerMultiFadeDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int lt = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        const float nextItemKoeff = 2.5f;
+        const float dots = (size.x / (thickness * nextItemKoeff));
+        const float heightSpeed = 0.8f;
+
+        for (size_t j = 0; j < dots; j++)
+        {
+            for (size_t i = 0; i < dots; i++)
+            {
+                float a = start - (IM_PI - i * j * PI_DIV(dots));
+                window->DrawList->AddCircleFilled(ImVec2(centre.x - (size.x / 2.f) + i * thickness * nextItemKoeff, centre.y - (size.y / 2.f) + j * thickness * nextItemKoeff), thickness, color_alpha(color, ImMax(0.1f, ImSin(a * heightSpeed))), lt);
+            }
+        }
+    }
+
+    inline void SpinnerScaleDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int lt = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 2.5f;
+        const float heightSpeed = 0.8f;
+        const float dots = (size.x / (thickness * nextItemKoeff));
+        const float start = (float)ImGui::GetTime() * speed;
+
+        for (size_t i = 0; i < dots; i++)
+        {
+          const float a = start + (IM_PI - i * PI_DIV(dots));
+          const float th = thickness * ImSin(a * heightSpeed);
+          window->DrawList->AddCircleFilled(ImVec2(centre.x - (size.x / 2.f) + i * thickness * nextItemKoeff, centre.y), thickness, color_alpha(color, 0.1f), lt);
+          window->DrawList->AddCircleFilled(ImVec2(centre.x - (size.x / 2.f) + i * thickness * nextItemKoeff, centre.y), th, color_alpha(color, 1.f), lt);
+        }
+    }
+
+    inline void SpinnerSquareSpins(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 2.5f;
+        const float heightSpeed = 0.8f;
+        const float dots = (size.x / (thickness * nextItemKoeff));
+        const float start = (float)ImGui::GetTime() * speed;
+
+        for (size_t i = 0; i < dots; i++)
+        {
+            const float a = ImFmod(start + i * ((PI_DIV_2 * 0.7f) / dots), PI_DIV_2);
+            const float th = thickness * (ImCos(a * heightSpeed) * 2.f);
+            ImVec2 pmin = ImVec2(centre.x - (size.x / 2.f) + i * thickness * nextItemKoeff - thickness, centre.y - thickness);
+            ImVec2 pmax = ImVec2(centre.x - (size.x / 2.f) + i * thickness * nextItemKoeff + thickness, centre.y + thickness);
+            window->DrawList->AddRect(pmin, pmax, color_alpha(color, 1.f), 0.f);
+            ImVec2 lmin = ImVec2(centre.x - (size.x / 2.f) + i * thickness * nextItemKoeff - thickness, centre.y - th + thickness);
+            ImVec2 lmax = ImVec2(centre.x - (size.x / 2.f) + i * thickness * nextItemKoeff + thickness - 1, centre.y - th + thickness);
+            window->DrawList->AddLine(lmin, lmax, color_alpha(color, 1.f), 1.f);
+        }
+    }
+
+    inline void SpinnerMovingDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t dots = 3)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 2.5f;
+        const float heightKoeff = 2.f;
+        const float heightSpeed = 0.8f;
+        const float start = ImFmod((float)ImGui::GetTime() * speed, size.x);
+
+        float offset = 0;
+        for (size_t i = 0; i < dots; i++)
+        {
+          float th = thickness;
+          offset = ImFmod(start + i * (size.x / dots), size.x);
+
+          if (offset < thickness) { th = offset; }
+          if (offset > size.x - thickness) { th = size.x - offset; }
+        
+          window->DrawList->AddCircleFilled(ImVec2(pos.x + offset - thickness, centre.y), th, color_alpha(color, 1.f), 8);
+        }
+    }
+
+    inline void SpinnerRotateDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int dots = 2, int mode = 0)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      ImGuiStorage* storage = window->DC.StateStorage;
+      const ImGuiID velocityId = window->GetID("##velocity");
+      const ImGuiID vtimeId = window->GetID("##velocitytime");
+
+      float velocity = storage->GetFloat(velocityId, 0.f);
+      float vtime = storage->GetFloat(vtimeId, 0.f);
+     
+      float dtime = ImFmod((float)vtime, IM_PI);
+      float start = (vtime += velocity);
+      if (dtime > 0.f && dtime < PI_DIV_2) { velocity += 0.001f * speed; }
+      else if (dtime > IM_PI * 0.9f && dtime < IM_PI) { velocity -= 0.01f * speed; }
+      if (velocity > 0.1f) velocity = 0.1f;
+      if (velocity < 0.01f) velocity = 0.01f;
+
+      storage->SetFloat(velocityId, velocity);
+      storage->SetFloat(vtimeId, vtime);
+
+      window->DrawList->AddCircleFilled(centre, thickness, color_alpha(color, 1.f), 8);
+
+      for (int i = 0; i < dots; i++)
+      {
+        float a = mode ? start + i * PI_2_DIV(dots) + damped_spring(1, 10.f, 1.0f, ImSin(start + i * PI_2_DIV(dots)), PI_2_DIV(dots), 0)
+                       : start + (i * PI_2_DIV(dots));
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius), thickness, color_alpha(color, 1.f), 8);
+      }
+    }
+
+    inline void SpinnerOrionDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int arcs = 4)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        ImGuiStorage* storage = window->DC.StateStorage;
+        const ImGuiID velocityId = window->GetID("##velocity");
+        const ImGuiID vtimeId = window->GetID("##velocitytime");
+
+        float velocity = storage->GetFloat(velocityId, 0.f);
+        float vtime = storage->GetFloat(vtimeId, 0.f);
+
+        float dtime = ImFmod((float)vtime, IM_PI);
+        float start = (vtime += velocity);
+        if (dtime > 0.f && dtime < PI_DIV_2) { velocity += 0.001f * speed; }
+        else if (dtime > IM_PI * 0.9f && dtime < IM_PI) { velocity -= 0.01f * speed; }
+
+        if (velocity > 0.1f) velocity = 0.1f;
+        if (velocity < 0.01f) velocity = 0.01f;
+
+        storage->SetFloat(velocityId, velocity);
+        storage->SetFloat(vtimeId, vtime);
+
+        window->DrawList->AddCircleFilled(centre, thickness, color_alpha(color, 1.f), 8);
+
+        for (int j = 1; j < arcs; ++j) {
+            const float r = (radius / (arcs + 1)) * j;
+            for (int i = 0; i < j + 1; i++)
+            {
+                const float a = start + (i * PI_2_DIV(j+1));
+                window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * r, centre.y + ImSin(a) * r), thickness, color_alpha(color, 1.f), 8);
+            }
+        }
+    }
+
+    inline void SpinnerGalaxyDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int arcs = 4)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        ImGuiStorage* storage = window->DC.StateStorage;
+        const ImGuiID velocityId = window->GetID("##velocity");
+        const ImGuiID vtimeId = window->GetID("##velocitytime");
+
+        float velocity = storage->GetFloat(velocityId, 0.f);
+        float vtime = storage->GetFloat(vtimeId, 0.f);
+
+        float dtime = ImFmod((float)vtime, IM_PI);
+        float start = (vtime += (velocity * speed));
+        if (dtime > 0.f && dtime < PI_DIV_2) { velocity += 0.001f; }
+        else if (dtime > IM_PI * 0.9f && dtime < IM_PI) { velocity -= 0.01f; }
+
+        if (velocity > 0.1f) velocity = 0.1f;
+        if (velocity < 0.01f) velocity = 0.01f;
+
+        storage->SetFloat(velocityId, velocity);
+        storage->SetFloat(vtimeId, vtime);
+
+        window->DrawList->AddCircleFilled(centre, thickness, color_alpha(color, 1.f), 8);
+
+        for (int j = 1; j < arcs; ++j) {
+            const float r = ((j / (float)arcs) * radius);
+            for (int i = 0; i < arcs; i++)
+            {
+                const float a = start * (1.f + j * 0.1f) + (i * PI_2_DIV(arcs));
+                window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * r, centre.y + ImSin(a) * r), thickness, color_alpha(color, 1.f), 8);
+            }
+        }
+    }
+
+    inline void SpinnerTwinAng(const char *label, float radius1, float radius2, float thickness, const ImColor &color1 = white, const ImColor &color2 = red, float speed = 2.8f, float angle = IM_PI)
+    {
+      const float radius = ImMax(radius1, radius2);
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+      const float aoffset = ImFmod((float)ImGui::GetTime(), 1.5f * IM_PI);
+      const float bofsset = (aoffset > angle) ? angle : aoffset;
+      const float angle_offset = angle * 2.f / num_segments;
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i <= 2 * num_segments; i++)
+      {
+        const float a = start + (i * angle_offset);
+        if (i * angle_offset > 2 * bofsset)
+          break;
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1));
+      }
+      window->DrawList->PathStroke(color_alpha(color1, 1.f), false, thickness);
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i < num_segments / 2; i++)
+      {
+        const float a = start + (i * angle_offset);
+        if (i * angle_offset > bofsset)
+          break;
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius2, centre.y + ImSin(a) * radius2));
+      }
+      window->DrawList->PathStroke(color_alpha(color2, 1.f), false, thickness);
+    }
+
+    inline void SpinnerFilling(const char *label, float radius, float thickness, const ImColor &color1 = white, const ImColor &color2 = red, float speed = 2.8f)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+      const float angle_offset = PI_2_DIV(num_segments - 1);
+
+      circle([&] (int i) {
+          const float a = (i * angle_offset);
+          return ImVec2(ImCos(a) * radius, ImSin(a) * radius);
+      }, color_alpha(color1, 1.f), thickness);
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i < 2 * num_segments / 2; i++)
+      {
+        const float a = (i * angle_offset);
+        if (a > start)
+          break;
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius));
+      }
+      window->DrawList->PathStroke(color_alpha(color2, 1.f), false, thickness);
+    }
+
+    inline void SpinnerFillingMem(const char *label, float radius, float thickness, const ImColor &color, ImColor &colorbg, float speed)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float angle_offset = PI_2_DIV(num_segments - 1);
+        num_segments *= 4;
+
+        circle([&] (int i) {
+            const float a = (i * angle_offset);
+            return ImVec2(ImCos(a) * radius, ImSin(a) * radius);
+        }, color_alpha(colorbg, 1.f), thickness);
+
+        if (start < 0.02f) {
+            colorbg = color;
+        }
+
+        window->DrawList->PathClear();
+        for (size_t i = 0; i < 2 * num_segments / 2; i++) {
+            const float a = (i * angle_offset);
+            if (a > start)
+                break;
+            window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius));
+        }
+        window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+    }
+
+    inline void SpinnerTopup(const char *label, float radius1, float radius2, const ImColor &color = red, const ImColor &fg = white, const ImColor &bg = white, float speed = 2.8f)
+    {
+      const float radius = ImMax(radius1, radius2);
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = ImFmod((float)ImGui::GetTime() * speed, IM_PI);
+      window->DrawList->AddCircleFilled(centre, radius1, color_alpha(bg, 1.f), num_segments);
+
+      const float abegin = (PI_DIV_2) - start;
+      const float aend = (PI_DIV_2) + start;
+      const float angle_offset = (aend - abegin) / num_segments;
+
+      window->DrawList->PathClear();
+      window->DrawList->PathArcTo(centre, radius1, abegin, aend, num_segments * 2);
+
+      ImDrawListFlags save = window->DrawList->Flags;
+      window->DrawList->Flags &= ~ImDrawListFlags_AntiAliasedFill;
+        
+      window->DrawList->PathFillConvex(color_alpha(color, 1.f));
+
+      window->DrawList->AddCircleFilled(centre, radius2, color_alpha(fg, 1.f), num_segments);
+
+      window->DrawList->Flags = save;
+    }
+
+    inline void SpinnerTwinAng180(const char *label, float radius1, float radius2, float thickness, const ImColor &color1 = white, const ImColor &color2 = red, float speed = 2.8f)
+    {
+      const float radius = ImMax(radius1, radius2);
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      num_segments *= 8;
+      const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+      const float aoffset = ImFmod((float)ImGui::GetTime(), PI_2);
+      const float bofsset = (aoffset > IM_PI) ? IM_PI : aoffset;
+      const float angle_offset = PI_2_DIV(num_segments);
+      float ared_min = 0, ared = 0;
+      if (aoffset > IM_PI)
+        ared_min = aoffset - IM_PI;
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i <= num_segments / 2 + 1; i++)
+      {
+        ared = start + (i * angle_offset);
+
+        if (i * angle_offset < ared_min)
+          continue;
+
+        if (i * angle_offset > bofsset)
+          break;
+
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ared) * radius2, centre.y + ImSin(ared) * radius2));
+      }
+      window->DrawList->PathStroke(color_alpha(color2, 1.f), false, thickness);
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i <= 2 * num_segments + 1; i++)
+      {
+        const float a = ared + ared_min + (i * angle_offset);
+        if (i * angle_offset < ared_min)
+          continue;
+
+        if (i * angle_offset > bofsset)
+          break;
+
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1));
+      }
+      window->DrawList->PathStroke(color_alpha(color1, 1.f), false, thickness);
+    }
+
+    inline void SpinnerTwinAng360(const char *label, float radius1, float radius2, float thickness, const ImColor &color1 = white, const ImColor &color2 = red, float speed1 = 2.8f, float speed2 = 2.5f, int mode = 0)
+    {
+      const float radius = ImMax(radius1, radius2);
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      num_segments *= 4;
+      float start1 = ImFmod((float)ImGui::GetTime() * speed1, PI_2);
+      float start2 = ImFmod((float)ImGui::GetTime() * speed2, PI_2);
+      const float aoffset = ImFmod((float)ImGui::GetTime(), 2.f * IM_PI);
+      const float bofsset = (aoffset > IM_PI) ? IM_PI : aoffset;
+      const float angle_offset = PI_2 / num_segments;
+      float ared_min = 0, ared = 0;
+      if (aoffset > IM_PI)
+        ared_min = aoffset - IM_PI;
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i <= num_segments + 1; i++) {
+        ared = ( mode ? damped_spring(1, 10.f, 1.0f, ImSin(ImFmod(start1 + 0 * PI_DIV(2), PI_2))) : start1) + (i * angle_offset);
+        if (i * angle_offset < ared_min * 2) continue;
+        if (i * angle_offset > bofsset * 2.f) break;
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ared) * radius2, centre.y + ImSin(ared) * radius2));
+      }
+      window->DrawList->PathStroke(color_alpha(color2, 1.f), false, thickness);
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i <= num_segments + 1; i++) {
+        ared = (mode ? damped_spring(1, 10.f, 1.0f, ImSin(ImFmod(start2 + 1 * PI_DIV(2), PI_2))) : start2) + (i * angle_offset);
+        if (i * angle_offset < ared_min * 2) continue;
+        if (i * angle_offset > bofsset * 2.f) break;
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(-ared) * radius1, centre.y + ImSin(-ared) * radius1));
+      }
+      window->DrawList->PathStroke(color_alpha(color1, 1.f), false, thickness);
+    }
+
+    inline void SpinnerIncDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t dots = 6)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float start = (float)ImGui::GetTime() * speed;
+      float astart = ImFmod(start, PI_DIV(dots));
+      start -= astart;
+      dots = ImMin<size_t>(dots, 32);
+
+      for (size_t i = 0; i <= dots; i++)
+      {
+        float a = start + (i * PI_DIV(dots - 1));
+        ImColor c = color_alpha(color, ImMax(0.1f, i / (float)dots));
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius), thickness, c, 8);
+      }
+    }
+
+    inline void SpinnerIncFullDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t dots = 4)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      dots = ImMin<size_t>(dots, 32);
+      float start = (float)ImGui::GetTime() * speed;
+      float astart = ImFmod(start, IM_PI / dots);
+      start -= astart;
+      const float bg_angle_offset = IM_PI / dots;
+
+      for (size_t i = 0; i < dots * 2; i++)
+      {
+        float a = start + (i * bg_angle_offset);
+        ImColor c = color_alpha(color, 0.1f);
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius), thickness, c, 8);
+      }
+
+      for (size_t i = 0; i < dots; i++)
+      {
+        float a = start + (i * bg_angle_offset);
+        ImColor c = color_alpha(color, ImMax(0.1f, i / (float)dots));
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius), thickness, c, 8);
+      }
+    }
+
+    inline void SpinnerFadeBars(const char *label, float w, const ImColor &color = white, float speed = 2.8f, size_t bars = 3, bool scale = false)
+    {
+      float radius = (w * 0.5f) * bars;
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      ImGuiContext &g = *GImGui;
+      const ImGuiStyle &style = g.Style;
+      const float nextItemKoeff = 1.5f;
+      const float yOffsetKoeftt = 0.8f;
+      const float heightSpeed = 0.8f;
+      const float start = (float)ImGui::GetTime() * speed;
+
+      const float offset = IM_PI / bars;
+      for (size_t i = 0; i < bars; i++)
+      {
+        float a = start + (IM_PI - i * offset);
+        ImColor c = color_alpha(color, ImMax(0.1f, ImSin(a * heightSpeed)));
+        float h = (scale ? (0.6f + 0.4f * c.Value.w) : 1.f) * size.y / 2;
+        window->DrawList->AddRectFilled(ImVec2(pos.x + style.FramePadding.x + i * (w * nextItemKoeff) - w / 2, centre.y - h * yOffsetKoeftt),
+                                        ImVec2(pos.x + style.FramePadding.x + i * (w * nextItemKoeff) + w / 2, centre.y + h * yOffsetKoeftt), c);
+      }
+    }
+
+    inline void SpinnerFadeTris(const char *label, float radius, const ImColor &color = white, float speed = 2.8f, size_t dim = 2, bool scale = false)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        ImGuiContext &g = *GImGui;
+        const ImGuiStyle &style = g.Style;
+        const float nextItemKoeff = 1.5f;
+        const float yOffsetKoeftt = 0.8f;
+        const float heightSpeed = 0.8f;
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+
+        std::vector<ImVec2> points;
+        auto pushPoints = [] (std::vector<ImVec2> &pp, const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3) { pp.push_back(p1); pp.push_back(p2); pp.push_back(p3); };
+        auto hsumPoints = [] (const ImVec2 &p1, const ImVec2 &p2) { return ImVec2((p1.x + p2.x) / 2.f, (p1.y + p2.y) / 2.f); };
+
+        auto splitTriangle = [&] (const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, int numDivisions) {
+            pushPoints(points, p1, p2, p3);
+
+            for (int i = 0; i < numDivisions; i++) {
+                std::vector<ImVec2> newPoints;
+                for (int j = 0; j < points.size() - 2; j += 3) {
+                    ImVec2 p1 = points[j];
+                    ImVec2 p2 = points[j + 1];
+                    ImVec2 p3 = points[j + 2];
+
+                    ImVec2 p4((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
+                    ImVec2 p5((p2.x + p3.x) / 2, (p2.y + p3.y) / 2);
+                    ImVec2 p6((p3.x + p1.x) / 2, (p3.y + p1.y) / 2);
+
+                    pushPoints(newPoints, p1, p4, p6);
+                    pushPoints(newPoints, p4, p5, p6);
+                    pushPoints(newPoints, p4, p2, p5);
+                    pushPoints(newPoints, p6, p5, p3);
+                }
+                points = newPoints;
+            }
+
+            return points;
+        };
+
+        auto calculateAngle = [] (ImVec2 v1, ImVec2 v2, const ImVec2 c) {
+            v1.x -= c.x; v1.y -= c.y; v2.x -= c.x; v2.y -= c.y;
+            float dotProduct = v1.x * v2.x + v1.y * v2.y;
+            float magnitudeV1 = ImSqrt(v1.x * v1.x + v1.y * v1.y);
+            float magnitudeV2 = ImSqrt(v2.x * v2.x + v2.y * v2.y);
+            float angleInRadians = ImAcos(dotProduct / (magnitudeV1 * magnitudeV2));
+            float crossProduct = v1.x * v2.y - v2.x * v1.y;
+            float signedAngle = std::copysign(angleInRadians, crossProduct);
+            return fmod(signedAngle + PI_2, PI_2);
+        };
+
+        const float offset = IM_PI / dim;
+        ImVec2 p1 = ImVec2(centre.x + ImSin(0) * radius, centre.y + ImCos(0) * radius);
+        ImVec2 p2 = ImVec2(centre.x + ImSin(PI_DIV(3) * 2) * radius, centre.y + ImCos(PI_DIV(3) * 2) * radius);
+        ImVec2 p3 = ImVec2(centre.x + ImSin(PI_DIV(3) * 4) * radius, centre.y + ImCos(PI_DIV(3) * 4) * radius);
+        std::vector<ImVec2> subdividedPoints = splitTriangle(p1, p2, p3, (int)dim);
+        for (size_t i = 0; i < subdividedPoints.size(); i+=3) {
+            ImVec2 trisCenter = hsumPoints(hsumPoints(subdividedPoints[i], subdividedPoints[i + 1]), subdividedPoints[i + 2]);
+            const float angle = calculateAngle(p1, trisCenter, centre);
+            ImColor c = color_alpha(color, 1.f - ImMax(0.1f, ImFmod(start + angle, PI_2) / (float)PI_2));
+            window->DrawList->AddTriangleFilled(subdividedPoints[i], subdividedPoints[i+1], subdividedPoints[i+2], c);
+        }
+    }
+
+    inline void SpinnerBarsRotateFade(const char *label, float rmin, float rmax , float thickness, const ImColor &color = white, float speed = 2.8f, size_t bars = 6)
+    {
+      float radius = rmax;
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float start = (float)ImGui::GetTime() * speed;
+      float astart = ImFmod(start, IM_PI / bars);
+      start -= astart;
+      const float bg_angle_offset = IM_PI / bars;
+      bars = ImMin<size_t>(bars, 32);
+
+      for (size_t i = 0; i <= bars; i++)
+      {
+        float a = start + (i * bg_angle_offset);
+        ImColor c = color_alpha(color, ImMax(0.1f, i / (float)bars));
+        window->DrawList->AddLine(ImVec2(centre.x + ImCos(a) * rmin, centre.y + ImSin(a) * rmin), ImVec2(centre.x + ImCos(a) * rmax, centre.y + ImSin(a) * rmax), c, thickness);
+      }
+    }
+
+    inline void SpinnerBarsScaleMiddle(const char *label, float w, const ImColor &color = white, float speed = 2.8f, size_t bars = 3)
+    {
+      float radius = (w) * bars;
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      ImGuiContext &g = *GImGui;
+      const ImGuiStyle &style = g.Style;
+      const float nextItemKoeff = 1.5f;
+      const float yOffsetKoeftt = 0.8f;
+      const float heightSpeed = 0.8f;
+      float start = (float)ImGui::GetTime() * speed;
+      const float offset = IM_PI / bars;
+
+      for (size_t i = 0; i < bars; i++)
+      {
+        float a = start + (IM_PI - i * offset);
+        float h = (0.4f + 0.6f * ImMax(0.1f, ImSin(a * heightSpeed))) * (size.y / 2);
+        window->DrawList->AddRectFilled(ImVec2(centre.x + style.FramePadding.x + i * (w * nextItemKoeff) - w / 2, centre.y - h * yOffsetKoeftt),
+                                        ImVec2(centre.x + style.FramePadding.x + i * (w * nextItemKoeff) + w / 2, centre.y + h * yOffsetKoeftt), color_alpha(color, 1.f));
+        if (i == 0)
+          continue;
+
+        window->DrawList->AddRectFilled(ImVec2(centre.x + style.FramePadding.x - i * (w * nextItemKoeff) - w / 2, centre.y - h * yOffsetKoeftt),
+                                        ImVec2(centre.x + style.FramePadding.x - i * (w * nextItemKoeff) + w / 2, centre.y + h * yOffsetKoeftt), color_alpha(color, 1.f));
+      }
+    }
+
+    inline void SpinnerAngTwin(const char *label, float radius1, float radius2, float thickness, const ImColor &color = white, const ImColor &bg = half_white, float speed = 2.8f, float angle = IM_PI, size_t arcs = 1, int mode = 0)
+    {
+      float radius = ImMax(radius1, radius2);
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float start = (float)ImGui::GetTime()* speed;
+      const float bg_angle_offset = PI_2 / num_segments;
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i <= num_segments; i++) {
+        const float a = start + (i * bg_angle_offset);
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1));
+      }
+      window->DrawList->PathStroke(color_alpha(bg, 1.f), false, thickness);
+
+      const float angle_offset = angle / num_segments;
+      for (size_t arc_num = 0; arc_num < arcs; ++arc_num) {
+          window->DrawList->PathClear();
+          float arc_start = 2 * IM_PI / arcs;
+          float b = mode ? start + damped_spring(1, 10.f, 1.0f, ImSin(ImFmod(start + arc_num * PI_DIV(2) / arcs, IM_PI)), 1, 0) : start;
+          for (size_t i = 0; i < num_segments; i++) {
+            const float a = b + arc_start * arc_num + (i * angle_offset);
+            window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius2, centre.y + ImSin(a) * radius2));
+          }
+          window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+      }
+    }
+
+    inline void SpinnerArcRotation(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4, int mode = 0)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime()* speed;
+      const float arc_angle = PI_2 / (float)arcs;
+      const float angle_offset = arc_angle / num_segments;
+      
+      for (size_t arc_num = 0; arc_num < arcs; ++arc_num) {
+        window->DrawList->PathClear();
+        ImColor c = color_alpha(color, ImMax(0.1f, arc_num / (float)arcs));
+        float b = mode ? start + damped_spring(1, 10.f, 1.0f, ImSin(ImFmod(start + arc_num * PI_DIV(2) / arcs, IM_PI)), 1, 0) : start;
+        for (size_t i = 0; i <= num_segments; i++) {
+          const float a = b + arc_angle * arc_num + (i * angle_offset);
+          window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius));
+        }
+        window->DrawList->PathStroke(c, false, thickness);
+      }
+    }
+
+    inline void SpinnerArcFade(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = ImFmod((float)ImGui::GetTime()* speed, IM_PI * 4.f);
+      const float arc_angle = PI_2 / (float)arcs;
+      const float angle_offset = arc_angle / num_segments;
+      
+      for (size_t arc_num = 0; arc_num < arcs; ++arc_num)
+      {
+        window->DrawList->PathClear();
+        for (size_t i = 0; i <= num_segments + 1; i++)
+        {
+          const float a = arc_angle * arc_num + (i * angle_offset) - PI_DIV_2 - PI_DIV_4;
+          window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius));
+        }
+        const float a = arc_angle * arc_num;
+        ImColor c = color;
+        if (start < PI_2) {
+          c.Value.w = 0.f;
+          if (start > a && start < (a + arc_angle)) { c.Value.w = 1.f - (start - a) / (float)arc_angle; }
+          else if (start < a) { c.Value.w = 1.f; }
+          c.Value.w = ImMax(0.05f, 1.f - c.Value.w);
+        } else {
+          const float startk = start - PI_2;
+          c.Value.w = 0.f;
+          if (startk > a && startk < (a + arc_angle)) { c.Value.w = 1.f - (startk - a) / (float)arc_angle; }
+          else if (startk < a) { c.Value.w = 1.f; }
+          c.Value.w = ImMax(0.05f, c.Value.w);
+        }
+       
+        window->DrawList->PathStroke(color_alpha(c, 1.f), false, thickness);
+      }
+    }
+
+    inline void SpinnerSimpleArcFade(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)     {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, IM_PI * 4.f);
+        const float arc_angle = PI_2 / (float)4;
+        const float angle_offset = arc_angle / num_segments;
+
+        auto draw_segment = [&] (int arc_num, float delta, auto c, float k, float t) {
+            window->DrawList->PathClear();
+            for (size_t i = 0; i <= num_segments + 1; i++) {
+                const float a = t * start + arc_angle * arc_num + (i * angle_offset) - PI_DIV_2 - PI_DIV_4 + delta;
+                window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius * k, centre.y + ImSin(a) * radius * k));
+            }
+            window->DrawList->PathStroke(color_alpha(c, 1.f), false, thickness);
+        };
+
+        for (size_t arc_num = 0; arc_num < 2; ++arc_num) {
+            const float a = arc_angle * arc_num;
+            ImColor c = color;
+            if (start < PI_2) {
+                c.Value.w = 0.f;
+                if (start > a && start < (a + arc_angle)) { c.Value.w = 1.f - (start - a) / (float)arc_angle; }
+                else if (start < a) { c.Value.w = 1.f; }
+                c.Value.w = ImMax(0.05f, 1.f - c.Value.w);
+            } else {
+                const float startk = start - PI_2;
+                c.Value.w = 0.f;
+                if (startk > a && startk < (a + arc_angle)) { c.Value.w = 1.f - (startk - a) / (float)arc_angle; }
+                else if (startk < a) { c.Value.w = 1.f; }
+                c.Value.w = ImMax(0.05f, c.Value.w);
+            }
+
+            draw_segment((int)arc_num, 0.f, c, 1.f + arc_num * 0.3f, arc_num > 0 ? -1.0f : 1.0f);
+            draw_segment((int)arc_num, IM_PI, c, 1.f + arc_num * 0.3f, arc_num > 0 ? -1.0f : 1.0f);
+        }
+    }
+
+    inline void SpinnerSquareStrokeFade(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, IM_PI * 4.f);
+        const float arc_angle = PI_DIV_2;
+        const float ht = thickness / 2.f;
+
+        for (size_t arc_num = 0; arc_num < 4; ++arc_num)
+        {
+            float a = arc_angle * arc_num;
+            ImColor c = color_alpha(color, 1.f);
+            if (start < PI_2) {
+                c.Value.w = (start > a && start < (a + arc_angle))
+                                ? 1.f - (start - a) / (float)arc_angle
+                                : (start < a ? 1.f : 0.f);
+                c.Value.w = ImMax(0.05f, 1.f - c.Value.w);
+            } else {
+                const float startk = start - PI_2;
+                c.Value.w = (startk > a && startk < (a + arc_angle))
+                                ? 1.f - (startk - a) / (float)arc_angle
+                                : (startk < a ? 1.f : 0.f);
+                c.Value.w = ImMax(0.05f, c.Value.w);
+            }
+            a -= PI_DIV_4;
+            const float r = radius * 1.4f;
+            const bool right = ImSin(a) > 0;
+            const bool top = ImCos(a) < 0;
+            ImVec2 p1(centre.x + ImCos(a) * r, centre.y + ImSin(a) * r);
+            ImVec2 p2(centre.x + ImCos(a - PI_DIV_2) * r, centre.y + ImSin(a - PI_DIV_2) * r);
+            switch (arc_num) {
+            case 0: p1.x -= ht; p2.x -= ht; break;
+            case 1: p1.y -= ht; p2.y -= ht; break;
+            case 2: p1.x += ht; p2.x += ht; break;
+            case 3: p1.y += ht; p2.y += ht; break;
+            }
+            window->DrawList->AddLine(p1, p2, color_alpha(c, 1.f), thickness);
+        }
+    }
+
+    inline void SpinnerAsciiSymbolPoints(const char *label, const char* text, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        if (!text || !*text)
+            return;
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, (float)strlen(text));
+        const ImFontGlyph* glyph = ImGui::GetCurrentContext()->Font->FindGlyph(text[(int)start]);
+
+        ImVec2 pp(centre.x - radius, centre.y - radius);
+        ImFontAtlas* atlas = ImGui::GetIO().Fonts;
+        unsigned char* bitmap;
+        int out_width, out_height;
+        atlas->GetTexDataAsAlpha8(&bitmap, &out_width, &out_height);
+
+        const int U1 = (int)(glyph->U1 * out_width);
+        const int U0 = (int)(glyph->U0 * out_width);
+        const int V1 = (int)(glyph->V1 * out_height);
+        const int V0 = (int)(glyph->V0 * out_height);
+        const float px = size.x / (U1 - U0);
+        const float py = size.y / (V1 - V0);
+        
+        for (int x = U0, ppx = 0; x < U1; x++, ppx++) {
+            for (int y = V0, ppy = 0; y < V1; y++, ppy++) {
+               ImVec2 point(pp.x + (ppx * px), pp.y + (ppy * py));
+               const unsigned char alpha = bitmap[out_width * y + x];
+               window->DrawList->AddCircleFilled(point, thickness * 1.5f, color_alpha({.5f,.5f,.5f,.5f}, alpha / 255.f));
+               window->DrawList->AddCircleFilled(point, thickness, color_alpha(color, alpha / 255.f));
+            }
+        }
+    }
+
+    inline void SpinnerTextFading(const char *label, const char* text, float radius, float fsize, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        if (!text || !*text)
+            return;
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const char *last_symbol = ImGui::FindRenderedTextEnd(text);
+        const ImVec2 text_size = ImGui::CalcTextSize(text, last_symbol);
+        const ImFont* font = ImGui::GetCurrentContext()->Font;
+
+        ImVec2 pp(centre.x - text_size.x / 2.f, centre.y - text_size.y / 2.f);
+
+        const int text_len = (int)(last_symbol - text);
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (int i = 0; text != last_symbol; ++text, ++i) {
+            const ImFontGlyph* glyph = ImGui::GetCurrentContext()->Font->FindGlyph(*text);
+            const float alpha = ImClamp(ImSin(-start + (i / (float)text_len * PI_DIV_2)), 0.f, 1.f);
+            ImColor c = ImColor::HSV(out_h + i * (1.f / text_len), out_s, out_v);
+            font->RenderChar(window->DrawList, fsize, pp, color_alpha(c, alpha), (ImWchar)*text);
+            pp.x += glyph->AdvanceX;
+        }
+    }
+
+    inline void SpinnerSevenSegments(const char *label, const char* text, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        if (!text || !*text)
+            return;
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, (float)strlen(text));
+
+        struct Segment { ImVec2 b, e; };
+        const float q = 1.f, hq = q * 0.5f, xq = thickness / radius;
+        const Segment segments[] = {{ ImVec2{-hq, 0.0f}, ImVec2{hq, 0.0f} }, /*central*/
+                                     { ImVec2{-hq - xq, 0.0f}, ImVec2{-hq - xq, -q} }, /*left up*/
+                                     { ImVec2{-hq - xq, q}, ImVec2{-hq - xq, xq} }, /*left down*/
+                                     { ImVec2{-hq, q}, ImVec2{hq, q} }, /*center up*/
+                                     { ImVec2{hq + xq, q}, ImVec2{hq + xq, xq} }, /*right down*/
+                                     { ImVec2{hq + xq, 0.0f}, ImVec2{hq + xq, -q} }, /*right up*/
+                                     { ImVec2{-hq, -q}, ImVec2{hq, -q} } /*center down*/};
+
+        const int symbols[][8]{
+            //segments
+            //g,f,e,d,c,b,a,sym
+            // NUMBERS
+            {0,1,1,1,1,1,1,0}, //ZERO
+            {0,0,0,0,1,1,0,1}, //ONE
+            {1,0,1,1,0,1,1,2}, //TWO
+            {1,0,0,1,1,1,1,3}, //THREE
+            {1,1,0,0,1,1,0,4}, //FOUR
+            {1,1,0,1,1,0,1,5}, //FIVE
+            {1,1,1,1,1,0,1,6}, //SIX
+            {0,0,0,0,1,1,1,7}, //SEVEN
+            {1,1,1,1,1,1,1,8}, //EIGHT
+            {1,1,0,1,1,1,1,9}, //NINE
+            // LETTERS
+            {1,1,1,0,1,1,1,'A'}, //LETTER A
+            {1,1,1,1,1,0,0,'B'}, //LETTER B
+            {0,1,1,1,0,0,1,'C'}, //LETTER C
+            {1,0,1,1,1,1,0,'D'}, //LETTER D
+            {1,1,1,1,0,0,1,'E'}, //LETTER E
+            {1,1,1,0,0,0,1,'F'}, //LETTER F
+            {0,1,1,1,1,0,1,'G'}, //LETTER G
+            {1,1,1,0,1,0,0,'H'}, //LETTER H
+            {0,1,1,0,0,0,0,'I'}, //LETTER I
+            {0,0,1,1,1,1,0,'J'}, //LETTER J
+            {1,1,1,0,1,0,1,'K'}, //LETTER K
+            {0,1,1,1,0,0,0,'L'}, //LETTER L
+            {0,0,1,0,1,0,1,'M'}, //LETTER M
+            {0,1,1,0,1,1,1,'N'}, //LETTER N
+            {0,1,1,1,1,1,1,'O'}, //LETTER O
+            {1,1,1,0,0,1,1,'P'}, //LETTER P
+            {1,1,0,0,1,1,1,'Q'}, //LETTER Q
+            {0,1,1,0,0,1,1,'R'}, //LETTER R
+            {1,1,0,1,1,0,1,'S'}, //LETTER S
+            {1,1,1,1,0,0,0,'T'}, //LETTER T
+            {0,1,1,1,1,1,0,'U'}, //LETTER U
+            {0,1,0,1,1,1,0,'V'}, //LETTER V
+            {0,1,0,1,0,1,0,'W'}, //LETTER W
+            {1,1,1,0,1,1,0,'X'}, //LETTER X
+            {1,1,0,1,1,1,0,'Y'}, //LETTER Y
+            {1,0,0,1,0,1,1,'Z'}, //LETTER Z
+        };
+
+        auto draw_segment = [&] (const Segment &s) {
+            ImVec2 p1(centre.x + radius * s.b.x, centre.y + radius * s.b.y);
+            ImVec2 p2(centre.x + radius * s.e.x, centre.y + radius * s.e.y);
+
+            window->DrawList->AddLine(p1, p2, color_alpha(color, 1.f), thickness);
+        };
+
+        auto draw_symbol = [&] (const int* sq) {
+            for (int i = 0; i < 7; ++i)
+                sq[i] ? draw_segment(segments[i]) : void();
+        };
+        char current_char = text[(int)start];
+        if (isalpha(current_char)) {
+            draw_symbol(symbols[tolower(current_char) - 'a' + 10]);
+        } else if (isdigit(current_char)) {
+            draw_symbol(symbols[current_char - '0']);
+        }
+    }
+
+    inline void SpinnerSquareStrokeFill(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float overt = 3.f;
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2 + overt);
+        const float arc_angle = 2.f * PI_DIV_4;
+        const float ht = thickness / 2.f;
+
+        const float r = radius * 1.4f;
+        ImVec2 pp(centre.x + ImCos(-IM_PI * 0.75f) * r, centre.y + ImSin(-IM_PI * 0.75f) * r);
+        if (start > PI_2) {
+            ImColor c = color;
+            const float delta = (start - PI_2) / overt;
+            if (delta < 0.5f) {
+                c.Value.w = 1.f - delta * 2.f;
+                window->DrawList->AddLine(ImVec2(pp.x - ht, pp.y), ImVec2(pp.x + ht, pp.y), color_alpha(c, 1.f), thickness);
+            } else {
+                c.Value.w = (delta - 0.5f) * 2.f;
+                window->DrawList->AddLine(ImVec2(pp.x - ht, pp.y), ImVec2(pp.x + ht, pp.y), color_alpha(color, 1.f), thickness);
+            }
+        } else {
+            window->DrawList->AddLine(ImVec2(pp.x - ht, pp.y), ImVec2(pp.x + ht, pp.y), color_alpha(color, 1.f), thickness);
+        }
+
+        if (start < PI_2) {
+            for (size_t arc_num = 0; arc_num < 4; ++arc_num) {
+                float a = arc_angle * arc_num;
+                float segment_progress = (start > a && start < (a + arc_angle))
+                    ? 1.f - (start - a) / (float)arc_angle
+                    : (start < a ? 1.f : 0.f);
+                a -= PI_DIV_4;
+                segment_progress = 1.f - segment_progress;
+                ImVec2 p1(centre.x + ImCos(a - PI_DIV_2) * r, centre.y + ImSin(a - PI_DIV_2) * r);
+                ImVec2 p2(centre.x + ImCos(a) * r, centre.y + ImSin(a) * r);
+                switch (arc_num) {
+                case 0: p1.x += ht; p2.x -= ht; p2.x = p1.x + (p2.x - p1.x) * segment_progress; break;
+                case 1: p1.y -= ht; p2.y -= ht; p2.y = p1.y + (p2.y - p1.y) * segment_progress; break;
+                case 2: p1.x += ht; p2.x += ht; p2.x = p1.x + (p2.x - p1.x) * segment_progress; break;
+                case 3: p1.y += ht; p2.y -= ht; p2.y = p1.y + (p2.y - p1.y) * segment_progress; break;
+                }
+                window->DrawList->AddLine(p1, p2, color_alpha(color, 1.f), thickness);
+            }
+        }
+    }
+
+    inline void SpinnerSquareStrokeLoading(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2 );
+        const float arc_angle = 2.f * PI_DIV_4;
+        const float ht = thickness / 2.f;
+
+        const float best_radius = radius * 1.4f;
+        const float delta = (start - PI_2) / 2.f;
+        for (size_t arc_num = 0; arc_num < 4; ++arc_num) {
+            float a = arc_angle * arc_num;
+            a -= PI_DIV_4;
+            ImVec2 pp(centre.x + ImCos(a) * best_radius, centre.y + ImSin(a) * best_radius);
+            window->DrawList->AddLine(ImVec2(pp.x - ht, pp.y), ImVec2(pp.x + ht, pp.y), color_alpha(color, 1.f), thickness);
+        }
+
+        const bool grow = start < IM_PI;
+        const float segment_progress = grow ? (start / IM_PI) : (1.f - (start - IM_PI) / IM_PI);
+        for (size_t arc_num = 0; arc_num < 4; ++arc_num)             {
+            float a = arc_angle * arc_num;
+            a -= PI_DIV_4;
+            const bool right = ImSin(a) > 0;
+            const bool top = ImCos(a) < 0;
+            ImVec2 p1(centre.x + ImCos(a - PI_DIV_2) * best_radius, centre.y + ImSin(a - PI_DIV_2) * best_radius);
+            ImVec2 p2(centre.x + ImCos(a) * best_radius, centre.y + ImSin(a) * best_radius);
+            switch (arc_num) {
+            case 0: p1.x -= ht; p2.x += ht;
+                p1.x = grow ? p1.x : p1.x + (p2.x - p1.x) * (1.f - segment_progress); p2.x = grow ? p1.x + (p2.x - p1.x) * segment_progress : p2.x; break;
+            case 1: p1.y -= ht; p2.y += ht;
+                p1.y = grow ? p1.y : p1.y + (p2.y - p1.y) * (1.f - segment_progress); p2.y = grow ? p1.y + (p2.y - p1.y) * segment_progress : p2.y; break;
+            case 2: p1.x += ht; p2.x -= ht;
+                p1.x = grow ? p1.x : grow ? p1.x : p1.x + (p2.x - p1.x) * (1.f - segment_progress); p2.x = grow ? p1.x + (p2.x - p1.x) * segment_progress : p2.x; break;
+            case 3: p1.y += ht; p2.y -= ht;
+                p1.y = grow ? p1.y : p1.y + (p2.y - p1.y) * (1.f - segment_progress); p2.y = grow ? p1.y + (p2.y - p1.y) * segment_progress : p2.y; break;
+            }
+            window->DrawList->AddLine(p1, p2, color_alpha(color, 1.f), thickness);
+        }
+    }
+
+    inline void SpinnerSquareLoading(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2 + PI_DIV_2 );
+        const float arc_angle = PI_DIV_2;
+        const float ht = thickness / 2.f;
+
+        const float best_radius = radius * 1.4f;
+        float a = arc_angle * 3 - PI_DIV_4 + (start > PI_2 ? start * 2.f : 0);
+        ImVec2 last_pos(centre.x + ImCos(a) * best_radius, centre.y + ImSin(a) * best_radius);
+        ImVec2 ppMin, ppMax;
+        for (size_t arc_num = 0; arc_num < 4; ++arc_num) {              
+            a = arc_angle * arc_num - PI_DIV_4 + (start > PI_2 ? start * 2.f : 0);
+            ImVec2 pp(centre.x + ImCos(a) * best_radius, centre.y + ImSin(a) * best_radius);
+            window->DrawList->AddLine(last_pos, pp, color_alpha(color, 1.f), thickness);
+            last_pos = pp;
+
+            if (start < PI_2) {
+                if (arc_num == 2) ppMin = ImVec2(centre.x + ImCos(a) * best_radius * 0.8f, centre.y + ImSin(a) * best_radius * 0.8f);
+                else if (arc_num == 0) ppMax = ImVec2(centre.x + ImCos(a) * best_radius * 0.8f, centre.y + ImSin(a) * best_radius * 0.8f);
+            }
+        }
+
+        if (start < PI_2) {
+            ppMax.y = ppMin.y + (start / PI_2) * (ppMax.y - ppMin.y);
+            window->DrawList->AddRectFilled(ppMin, ppMax, color_alpha(color, 1.f), 0.f);
+        }
+    }
+
+    inline void SpinnerFilledArcFade(const char *label, float radius, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4, int mode = 0)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = ImFmod((float)ImGui::GetTime()* speed, IM_PI * 4.f);
+      const float arc_angle = PI_2 / (float)arcs;
+      const float angle_offset = arc_angle / num_segments;
+      for (size_t arc_num = 0; arc_num < arcs; ++arc_num)
+      {
+        const float b = arc_angle * arc_num - PI_DIV_2 - PI_DIV_4;
+        const float e = arc_angle * arc_num + arc_angle - PI_DIV_2 - PI_DIV_4;
+        const float a = arc_angle * arc_num;
+        ImColor c = color;
+        float vradius = radius;
+        if (start < PI_2) {
+          c.Value.w = 0.f;
+          if (start > a && start < (a + arc_angle)) { c.Value.w = 1.f - (start - a) / (float)arc_angle; }
+          else if (start < a) { c.Value.w = 1.f; }
+          c.Value.w = ImMax(0.f, 1.f - c.Value.w);
+          if (mode == 1)
+            vradius = radius * c.Value.w;
+        }
+        else
+        {
+          const float startk = start - PI_2;
+          c.Value.w = 0.f;
+          if (startk > a && startk < (a + arc_angle)) { c.Value.w = 1.f - (startk - a) / (float)arc_angle; }
+          else if (startk < a) { c.Value.w = 1.f; }
+          if (mode == 1)
+            vradius = radius * c.Value.w;
+        }
+
+        window->DrawList->PathClear();
+        window->DrawList->PathLineTo(centre);
+        for (size_t i = 0; i <= num_segments + 1; i++)
+        {
+          const float ar = arc_angle * arc_num + (i * angle_offset) - PI_DIV_2 - PI_DIV_4;
+          window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ar) * vradius, centre.y + ImSin(ar) * vradius));
+        }
+        window->DrawList->PathFillConvex(color_alpha(c, 1.f));
+      }
+    }
+
+    inline void SpinnerPointsArcBounce(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t points = 4, int circles = 2, float rspeed = 0.f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime()* speed, IM_PI * 4.f);
+        const float arc_angle = PI_2 / (float)points;
+        const float angle_offset = arc_angle / num_segments;
+        float dspeed = rspeed;
+        for (int c_num = 0; c_num < circles; c_num++)
+        {
+            float mr = radius * (1.f - (1.f / (circles + 2.f) * c_num));
+            float adv_angle = IM_PI * c_num;// *(1.f + (0.1f * circles) * c_num);
+            for (size_t arc_num = 0; arc_num < points; ++arc_num)
+            {
+                const float b = arc_angle * arc_num - PI_DIV_2 - PI_DIV_4;
+                const float e = arc_angle * arc_num + arc_angle - PI_DIV_2 - PI_DIV_4;
+                const float a = arc_angle * arc_num;
+                ImColor c = color;
+                float vradius = mr;
+                if (start < PI_2) {
+                    c.Value.w = 0.f;
+                    if (start > a && start < (a + arc_angle)) { c.Value.w = 1.f - (start - a) / (float)arc_angle; }
+                    else if (start < a) { c.Value.w = 1.f; }
+                    c.Value.w = ImMax(0.f, 1.f - c.Value.w);
+                     vradius = mr * c.Value.w;
+                }
+                else
+                {
+                    const float startk = start - PI_2;
+                    c.Value.w = 0.f;
+                    if (startk > a && startk < (a + arc_angle)) { c.Value.w = 1.f - (startk - a) / (float)arc_angle; }
+                    else if (startk < a) { c.Value.w = 1.f; }
+                    vradius = mr * c.Value.w;
+                }
+
+                const float ar = start * dspeed + adv_angle + arc_angle * arc_num - PI_DIV_2 - PI_DIV_4;
+                window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(ar) * vradius, centre.y + ImSin(ar) * vradius), thickness, color_alpha(c, 1.f), 8);
+            }
+            dspeed += rspeed;
+        }
+    }
+
+    inline void SpinnerFilledArcColor(const char *label, float radius, const ImColor &color = red, const ImColor &bg = white, float speed = 2.8f, size_t arcs = 4)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = ImFmod((float)ImGui::GetTime()* speed, PI_2);
+      const float arc_angle = PI_2 / (float)arcs;
+      const float angle_offset = arc_angle / num_segments;
+
+      window->DrawList->AddCircleFilled(centre, radius, color_alpha(bg, 1.f), num_segments * 2);
+      for (size_t arc_num = 0; arc_num < arcs; ++arc_num)
+      {
+        const float b = arc_angle * arc_num - PI_DIV_2;
+        const float e = arc_angle * arc_num + arc_angle - PI_DIV_2;
+        const float a = arc_angle * arc_num;
+
+        ImColor c = color;
+        c.Value.w = 0.f;
+        if (start > a && start < (a + arc_angle)) { c.Value.w = 1.f - (start - a) / (float)arc_angle; }
+        else if (start < a) { c.Value.w = 1.f; }
+        c.Value.w = ImMax(0.f, 1.f - c.Value.w);
+        
+        window->DrawList->PathClear();
+        window->DrawList->PathLineTo(centre);
+        for (size_t i = 0; i < num_segments + 1; i++)
+        {
+          const float ar = arc_angle * arc_num + (i * angle_offset) - PI_DIV_2;
+          window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ar) * radius, centre.y + ImSin(ar) * radius));
+        }
+        window->DrawList->PathFillConvex(color_alpha(c, 1.f));
+      }
+    }
+
+    inline void SpinnerFilledArcRing(const char *label, float radius, float thickness, const ImColor &color = red, const ImColor &bg = white, float speed = 2.8f, size_t arcs = 4)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float pi_div_2 = PI_DIV_2;
+        const float pi_div_4 = PI_DIV_4;
+        const float pi_mul_2 = PI_2;
+        const float start = ImFmod((float)ImGui::GetTime() * speed, pi_mul_2 + pi_div_4);
+        const float arc_angle = pi_mul_2 / (float)arcs;
+        const float angle_offset = arc_angle / num_segments;
+
+        window->DrawList->PathClear();
+        for (size_t i = 0; i < num_segments + 1; i++)
+        {
+            const float ar_b = (i * (pi_mul_2 / num_segments));
+            window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ar_b) * radius, centre.y + ImSin(ar_b) * radius));
+        }
+        window->DrawList->PathStroke(color_alpha(bg, 1.f), false, thickness);
+
+        for (size_t arc_num = 0; arc_num < arcs; ++arc_num)
+        {
+            const float b = arc_angle * arc_num - pi_div_2;
+            const float e = arc_angle * arc_num + arc_angle - pi_div_2;
+            const float a = arc_angle * arc_num;
+
+            float alpha = 0.f;
+            if (start > pi_mul_2) { alpha = (start - pi_mul_2) / pi_div_4; }
+            else if (start > a && start < (a + arc_angle)) { alpha = 1.f - (start - a) / (float)arc_angle; }
+            else if (start < a) { alpha = 1.f; }
+
+            window->DrawList->PathClear();
+            for (size_t i = 0; i < num_segments + 1; i++)
+            {
+                const float ar_b = arc_angle * arc_num + (i * angle_offset) - pi_div_2;
+                window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ar_b) * radius, centre.y + ImSin(ar_b) * radius));
+            }
+            window->DrawList->PathStroke(color_alpha(color, ImMax(0.f, 1.f - alpha)), false, thickness);
+        }
+    }
+
+    inline void SpinnerArcWedges(const char *label, float radius, const ImColor &color = red, float speed = 2.8f, size_t arcs = 4)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        const float arc_angle = PI_2 / (float)arcs;
+        const float angle_offset = arc_angle / num_segments;
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+
+        for (size_t arc_num = 0; arc_num < arcs; ++arc_num)
+        {
+            const float b = arc_angle * arc_num - PI_DIV_2;
+            const float e = arc_angle * arc_num + arc_angle - PI_DIV_2;
+            const float a = arc_angle * arc_num;
+
+            window->DrawList->PathClear();
+            window->DrawList->PathLineTo(centre);
+            for (size_t i = 0; i < num_segments + 1; i++)
+            {
+                const float start_a = ImFmod(start * (1.05f * (arc_num + 1)), PI_2);
+                const float ar = start_a + arc_angle * arc_num + (i * angle_offset) - PI_DIV_2;
+                window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ar) * radius, centre.y + ImSin(ar) * radius));
+            }
+            window->DrawList->PathFillConvex(color_alpha(ImColor::HSV(out_h + (1.f / arcs) * arc_num, out_s, out_v, 0.7f), 1.f));
+        }
+    }
+
+    inline void SpinnerTwinBall(const char *label, float radius1, float radius2, float thickness, float b_thickness, const ImColor &ball = white, const ImColor &bg = half_white, float speed = 2.8f, size_t balls = 2)
+    {
+      float radius = ImMax(radius1, radius2);
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime()* speed;
+      const float bg_angle_offset = PI_2 / num_segments;
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i <= num_segments; i++)
+      {
+        const float a = start + (i * bg_angle_offset);
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1));
+      }
+      window->DrawList->PathStroke(color_alpha(bg, 1.f), false, thickness);
+
+      for (size_t b_num = 0; b_num < balls; ++b_num)
+      {
+        float b_start = PI_2 / balls;
+        const float a = b_start * b_num + start;
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius2, centre.y + ImSin(a) * radius2), b_thickness, color_alpha(ball, 1.f));
+      }
+    }
+
+    inline void SpinnerSolarBalls(const char *label, float radius, float thickness, const ImColor &ball = white, const ImColor &bg = half_white, float speed = 2.8f, size_t balls = 4)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        const float bg_angle_offset = PI_2 / num_segments;
+
+        for (int i = 0; i < balls; ++i) {
+            const float rb = (radius / balls) * 1.3f * (i + 1);
+            window->DrawList->AddCircle(centre, rb, color_alpha(bg, 1.f), num_segments, thickness * 0.3f);
+        }
+
+        for (int i = 0; i < balls; ++i) {
+            const float rb = (radius / balls) * 1.3f * (i + 1);
+            const float a = start * (1.0f + 0.1f * i);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * rb, centre.y + ImSin(a) * rb), thickness, color_alpha(ball, 1.f));
+        }
+    }
+
+    inline void SpinnerSolarScaleBalls(const char *label, float radius, float thickness, const ImColor &ball = white, float speed = 2.8f, size_t balls = 4)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, IM_PI * 16.f);
+        const float bg_angle_offset = PI_2 / num_segments;
+
+        for (int i = 0; i < balls; ++i) {
+            const float rb = (radius / balls) * 1.3f * (i + 1);
+            const float a = start * (1.0f + 0.1f * i);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * rb, centre.y + ImSin(a) * rb), ((thickness * 2.f) / balls) * i, color_alpha(ball, 1.f));
+        }
+    }
+
+    inline void SpinnerSolarArcs(const char *label, float radius, float thickness, const ImColor &ball = white, const ImColor &bg = half_white, float speed = 2.8f, size_t balls = 4)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime()* speed;
+        const int half_segments = num_segments / 2;
+
+        for (int i = 0; i < balls; ++i)
+        {
+            const float rb = (radius / balls) * 1.3f * (i + 1);
+            const float bg_angle_offset = IM_PI / half_segments;
+            const int mul = i % 2 ? -1 : 1;
+            window->DrawList->PathClear();
+            for (size_t ii = 0; ii <= half_segments; ii++)
+            {
+                const float a = (ii * bg_angle_offset);
+                window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * rb, centre.y + ImSin(a) * rb * mul));
+            }
+            window->DrawList->PathStroke(color_alpha(bg, 1.f), false, thickness * 0.8f);
+        }
+
+        for (int i = 0; i < balls; ++i)
+        {
+            const float rb = (radius / balls) * 1.3f * (i + 1);
+            const float a = ImFmod(start * (1.0f + 0.1f * i), PI_2);
+            const int mul = i % 2 ? -1 : 1;
+            float y = ImSin(a) * rb;
+            if ((y > 0 && mul < 0) || (y < 0 && mul > 0)) y = -y;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * rb, centre.y + y), thickness, color_alpha(ball, 1.f));
+        }
+    }
+
+    inline void SpinnerMovingArcs(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, IM_PI * 2);
+        const int half_segments = num_segments / 2;
+
+        for (int i = 0; i < arcs; ++i) {
+            const float rb = (radius / arcs) * 1.3f * (i + 1);
+            float a = damped_spring(1, 10.f, 1.0f, ImSin(ImFmod(start + i * PI_DIV(arcs), PI_2)));
+            const float angle = ImMax(PI_DIV_2, (1.f - i/(float)arcs) * IM_PI);
+            circle([&] (int i) {
+                const float b = a + (i * angle / num_segments);
+                return ImVec2(ImCos(b) * rb, ImSin(b) * rb);
+            }, color_alpha(color, 1.f), thickness);
+        }
+    }
+
+    inline void SpinnerRainbowCircle(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4, float mode = 1)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        num_segments *= 2;
+        const float bg_angle_offset = IM_PI / num_segments;
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+
+        for (int i = 0; i < arcs; ++i)
+        {
+            float max_angle = IM_PI - ImFmod(start /* * (1.0 + 0.1f * i) */, IM_PI + PI_DIV_2 + PI_DIV(8));
+            max_angle = ImMin(IM_PI, max_angle + (PI_DIV_2 / arcs) * i);
+            const float rb = (radius / arcs) * 1.1f * (i + 1);
+
+            ImColor c = ImColor::HSV(out_h + i * (0.8f / arcs), out_s, out_v);
+            const int draw_segments = ImMax<int>(0, (int)(max_angle / bg_angle_offset));
+
+            for (int j = 0; j < 2; j++)
+            {
+                const int mul = j % 2 ? -1 : 1;
+                const float py = (j % 2 ? -0.5f : 0.5f) * thickness;
+                const float alpha_start = (j % 2 ? 0 : IM_PI) * mode;
+                window->DrawList->PathClear();
+                for (size_t ii = 0; ii <= draw_segments + 1; ii++)
+                {
+                    const float a = (ii * bg_angle_offset);
+                    window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(IM_PI - alpha_start - a) * rb, centre.y + ImSin(a) * rb * mul + py));
+                }
+                window->DrawList->PathStroke(color_alpha(c, 1.f), false, thickness * 0.8f);
+            }
+        }
+    }
+
+    inline void SpinnerBounceBall(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int dots = 1, bool shadow = false)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      ImGuiStorage* storage = window->DC.StateStorage;
+      const ImGuiID vtimeId = window->GetID("##vtime");
+      const ImGuiID hmaxId = window->GetID("##hmax");
+
+      float vtime = storage->GetFloat(vtimeId, 0.f);
+      float hmax = storage->GetFloat(hmaxId, 1.f);
+
+      vtime += 0.05f;
+      hmax += 0.01f;
+
+      storage->SetFloat(vtimeId, vtime);
+      storage->SetFloat(hmaxId, hmax);
+
+      constexpr float rkoeff[9] = {0.1f, 0.15f, 0.17f, 0.25f, 0.31f, 0.19f, 0.08f, 0.24f, 0.9f};
+      const int iterations = shadow ? 4 : 1;
+      for (int j = 0; j < iterations; j++) {
+          ImColor c = color_alpha(color, 1.f - 0.15f * j);
+          for (int i = 0; i < dots; i++) {
+              float start = ImFmod((float)ImGui::GetTime() * speed * (1 + rkoeff[i % 9]) - (IM_PI / 12.f) * j, IM_PI);
+              float sign = ((i % 2 == 0) ? 1.f : -1.f);
+              float offset = (i == 0) ? 0.f : (floorf((i+1) / 2.f + 0.1f) * sign * 2.f * thickness);
+              float maxht = damped_gravity(ImSin(ImFmod(hmax, IM_PI))) * radius;
+              window->DrawList->AddCircleFilled(ImVec2(centre.x + offset, centre.y + radius - ImSin(start) * 2.f * maxht), thickness, c, 8);
+          }
+      }
+    }
+
+    inline void SpinnerPulsarBall(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, bool shadow = false, int mode = 0)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        ImGuiStorage* storage = window->DC.StateStorage;
+
+        const int iterations = shadow ? 4 : 1;
+        for (int j = 0; j < iterations; j++) {
+            ImColor c = color_alpha(color, 1.f - 0.15f * j);
+            float start = ImFmod((float)ImGui::GetTime() * speed - (IM_PI / 12.f) * j, IM_PI);
+            float maxht = damped_gravity(ImSin(ImFmod(start, IM_PI))) * (radius * 0.6f);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x, centre.y), maxht, c, num_segments);
+        }
+
+        const float angle_offset = PI_DIV_2 / num_segments;
+        const int arcs = 2;
+        for (size_t arc_num = 0; arc_num < arcs; ++arc_num) {
+            window->DrawList->PathClear();
+            float arc_start = 2 * IM_PI / arcs;
+            float start = ImFmod((float)ImGui::GetTime() * speed - (IM_PI * arc_num), IM_PI);
+            float b = mode ? start + damped_spring(1, 10.f, 1.0f, ImSin(ImFmod(start + arc_num * PI_DIV(2) / arcs, IM_PI)), 1, 0) : start;
+            float maxht = (damped_gravity(ImSin(ImFmod(start, IM_PI))) * 0.3f + 0.7f) * radius;
+            for (size_t i = 0; i < num_segments; i++) {
+                const float a = b + arc_start * arc_num + (i * angle_offset);
+                window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * maxht, centre.y + ImSin(a) * maxht));
+            }
+            window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+        }
+    }
+
+    inline void SpinnerIncScaleDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t dots = 6)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float start = (float)ImGui::GetTime() * speed;
+      float astart = ImFmod(start, IM_PI / dots);
+      start -= astart;
+      const float bg_angle_offset = IM_PI / dots;
+      dots = ImMin(dots, (size_t)32);
+
+      for (size_t i = 0; i <= dots; i++)
+      {
+        float a = start + (i * bg_angle_offset);
+        float th = thickness * ImMax(0.1f, i / (float)dots);
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius), th, color_alpha(color, 1.f), 8);
+      }
+    }
+
+    inline void SpinnerSomeScaleDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t dots = 6, int mode = 0)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        float start = (float)ImGui::GetTime() * speed;
+        float astart = ImFmod(start, IM_PI / dots);
+        start -= astart;
+        const float bg_angle_offset = IM_PI / dots;
+        dots = ImMin(dots, (size_t)32);
+
+        for (size_t j = 0; j < 4; j++)
+        {
+            float r = radius * (1.f - (0.15f * j));
+            for (size_t i = 0; i <= dots; i++)
+            {
+                float a = start * (mode ? (1.f + j * 0.05f) : 1.f) + (i * bg_angle_offset);
+                float th = thickness * ImMax(0.1f, i / (float)dots);
+                float thh = th * (1.f - (0.2f * j));
+                window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * r, centre.y + ImSin(a) * r), thh, color_alpha(color, 1.f), 8);
+            }
+        }
+    }
+
+    inline void SpinnerAngTriple(const char *label, float radius1, float radius2, float radius3, float thickness, const ImColor &c1 = white, const ImColor &c2 = half_white, const ImColor &c3 = white, float speed = 2.8f, float angle = IM_PI)
+    {
+      float radius = ImMax(ImMax(radius1, radius2), radius3);
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start1 = (float)ImGui::GetTime() * speed;
+      const float angle_offset = angle / num_segments;
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i < num_segments; i++)
+      {
+        const float a = start1 + (i * angle_offset);
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1));
+      }
+      window->DrawList->PathStroke(color_alpha(c1, 1.f), false, thickness);
+
+      float start2 = (float)ImGui::GetTime() * 1.2f * speed;
+      window->DrawList->PathClear();
+      for (size_t i = 0; i < num_segments; i++)
+      {
+        const float a = start2 + (i * angle_offset);
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(-a) * radius2, centre.y + ImSin(-a) * radius2));
+      }
+      window->DrawList->PathStroke(color_alpha(c2, 1.f), false, thickness);
+
+      float start3 = (float)ImGui::GetTime() * 0.9f * speed;
+      window->DrawList->PathClear();
+      for (size_t i = 0; i < num_segments; i++)
+      {
+        const float a = start3 + (i * angle_offset);
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius3, centre.y + ImSin(a) * radius3));
+      }
+      window->DrawList->PathStroke(color_alpha(c3, 1.f), false, thickness);
+    }
+
+    inline void SpinnerAngEclipse(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, float angle = IM_PI)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime()* speed;
+      const float angle_offset = angle / num_segments;
+      const float th = thickness / num_segments;
+
+      for (size_t i = 0; i < num_segments; i++)
+      {
+        const float a = start + (i * angle_offset);
+        const float a1 = start + ((i+1) * angle_offset);
+        window->DrawList->AddLine(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius),
+                                  ImVec2(centre.x + ImCos(a1) * radius, centre.y + ImSin(a1) * radius),
+                                  color_alpha(color, 1.f),
+                                  th * i);
+      }
+    }
+
+    inline void SpinnerIngYang(const char *label, float radius, float thickness, bool reverse, float yang_detlta_r, const ImColor &colorI = white, const ImColor &colorY = white, float speed = 2.8f, float angle = IM_PI * 0.7f)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float startI = (float)ImGui::GetTime() * speed;
+      const float startY = (float)ImGui::GetTime() * (speed + (yang_detlta_r > 0.f ? ImClamp(yang_detlta_r * 0.5f, 0.5f, 2.f) : 0.f));
+      const float angle_offset = angle / num_segments;
+      const float th = thickness / num_segments;
+
+      for (int i = 0; i < num_segments; i++)
+      {
+        const float a = startI + (i * angle_offset);
+        const float a1 = startI + ((i + 1) * angle_offset);
+        window->DrawList->AddLine(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius),
+                                  ImVec2(centre.x + ImCos(a1) * radius, centre.y + ImSin(a1) * radius),
+                                  color_alpha(colorI, 1.f),
+                                  th * i);
+      }
+      const float ai_end = startI + (num_segments * angle_offset);
+      ImVec2 circle_i_center{centre.x + ImCos(ai_end) * radius, centre.y + ImSin(ai_end) * radius};
+      window->DrawList->AddCircleFilled(circle_i_center, thickness / 2.f, color_alpha(colorI, 1.f), num_segments);
+
+      const float rv = reverse ? -1.f : 1.f;
+      const float yang_radius = (radius - yang_detlta_r);
+      for (int i = 0; i < num_segments; i++)
+      {
+        const float a = startY + IM_PI + (i * angle_offset);
+        const float a1 = startY + IM_PI + ((i+1) * angle_offset);
+        window->DrawList->AddLine(ImVec2(centre.x + ImCos(a * rv) * yang_radius, centre.y + ImSin(a * rv) * yang_radius),
+                                  ImVec2(centre.x + ImCos(a1 * rv) * yang_radius, centre.y + ImSin(a1 * rv) * yang_radius),
+                                  color_alpha(colorY, 1.f),
+                                  th * i);
+      }
+      const float ay_end = startY + IM_PI + (num_segments * angle_offset);
+      ImVec2 circle_y_center{centre.x + ImCos(ay_end * rv) * yang_radius, centre.y + ImSin(ay_end * rv) * yang_radius};
+      window->DrawList->AddCircleFilled(circle_y_center, thickness / 2.f, color_alpha(colorY, 1.f), num_segments);
+    }
+
+
+    inline void SpinnerGooeyBalls(const char *label, float radius, const ImColor &color, float speed, int mode = 0)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float start = ImFmod((float)ImGui::GetTime() * speed, IM_PI);
+      start = mode ? damped_spring(1, 10.f, 1.0f, ImSin(start), 1, 0) : start;
+      const float radius1 = (0.4f + 0.3f * ImSin(start)) * radius;
+      const float radius2 = radius - radius1;
+
+      window->DrawList->AddCircleFilled(ImVec2(centre.x - radius + radius1, centre.y), radius1, color_alpha(color, 1.f), num_segments);
+      window->DrawList->AddCircleFilled(ImVec2(centre.x - radius + radius1 * 1.2f + radius2, centre.y), radius2, color_alpha(color, 1.f), num_segments);
+    }
+
+    inline void SpinnerDotsLoading(const char *label, float radius, float thickness, const ImColor &color, const ImColor &bg, float speed)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, IM_PI);
+        const float radius1 = (2.f * ImSin(start)) * radius;
+
+        float startb = ImFmod(start, PI_DIV_2);
+        float lenb = startb < PI_DIV_2 ? ImAbs((0.5f * ImSin(start * 2)) * radius) : radius * 0.5f;
+        float radius2 = radius * 0.25f;
+
+        float deltae = thickness - ImMin(thickness, ImMax<float>(0, (2.f * radius - radius1 + thickness + lenb) * 0.25f));
+        float deltag = ImMin(thickness, ImAbs(centre.x - radius + radius1 + thickness + lenb - centre.x - radius) * 0.25f);
+        window->DrawList->AddCircleFilled(ImVec2(centre.x - radius, centre.y), radius2 + deltag, color_alpha(bg, 1.f), num_segments);
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + radius + thickness, centre.y), radius2 + deltae, color_alpha(bg, 1.f), num_segments);
+
+        window->DrawList->AddRectFilled(ImVec2(centre.x - radius + radius1 - thickness - lenb, centre.y - thickness), ImVec2(centre.x - radius + radius1 + thickness + lenb, centre.y + thickness), color_alpha(color, 1.f), thickness);
+    }
+
+    inline void SpinnerRotateGooeyBalls(const char *label, float radius, float thickness, const ImColor &color, float speed, int balls)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = ImFmod((float)ImGui::GetTime(), IM_PI);
+      const float rstart = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+      const float radius1 = (0.2f + 0.3f * ImSin(start)) * radius;
+      const float angle_offset = PI_2 / balls;
+
+      for (int i = 0; i <= balls; i++)
+      {
+        const float a = rstart + (i * angle_offset);
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1), thickness, color_alpha(color, 1.f), num_segments);
+      }
+    }
+
+    inline void SpinnerHerbertBalls(const char *label, float radius, float thickness, const ImColor &color, float speed, int balls)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime(), IM_PI);
+        const float rstart = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float radius1 = 0.3f * radius;
+        const float radius2 = 0.8f * radius;
+        const float angle_offset = PI_2 / balls;
+
+        for (int i = 0; i < balls; i++)
+        {
+            const float a = rstart + (i * angle_offset);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1), thickness, color_alpha(color, 1.f), num_segments);
+        }
+
+        for (int i = 0; i < balls * 2; i++)
+        {
+            const float a = -rstart + (i * angle_offset / 2.f);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius2, centre.y + ImSin(a) * radius2), thickness, color_alpha(color, 1.f), num_segments);
+        }
+    }
+
+    inline void SpinnerHerbertBalls3D(const char *label, float radius, float thickness, const ImColor &color, float speed)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime(), IM_PI);
+        const float rstart = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float radius1 = 0.3f * radius;
+        const float radius2 = 0.8f * radius;
+        const int balls = 2;
+        const float angle_offset = PI_2 / balls;
+
+        ImVec2 frontpos, backpos;
+        for (int i = 0; i < balls; i++)
+        {
+            const float a = rstart + (i * angle_offset);
+            const float t = (i == 1 ? 0.7f : 1.f) * thickness;
+            const ImVec2 pos = ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1);
+            window->DrawList->AddCircleFilled(pos, t, color_alpha(color, 1.f), num_segments);
+            if (i == 0) frontpos = pos; else backpos = pos;
+        }
+
+        ImVec2 lastpos;
+        for (int i = 0; i <= balls * 2; i++)
+        {
+            const float a = -rstart + (i * angle_offset / 2.f);
+            const ImVec2 pos = ImVec2(centre.x + ImCos(a) * radius2, centre.y + ImSin(a) * radius2);
+            float t = (float)sqrt(pow(pos.x - frontpos.x, 2) + pow(pos.y - frontpos.y, 2)) / (radius * 1.f) * thickness;
+            window->DrawList->AddCircleFilled(pos, t, color_alpha(color, 1.f), num_segments);
+            window->DrawList->AddLine(pos, backpos, color_alpha(color, 0.5f), ImMax(thickness / 2.f, 1.f));
+            if (i > 0) {
+              window->DrawList->AddLine(pos, lastpos, color_alpha(color, 1.f), ImMax(thickness / 2.f, 1.f));
+            }
+            window->DrawList->AddLine(pos, frontpos, color_alpha(color, 1.f), ImMax(thickness / 2.f, 1.f));
+            lastpos = pos;
+        }
+    }
+
+    inline void SpinnerRotateTriangles(const char *label, float radius, float thickness, const ImColor &color, float speed, int tris)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = ImFmod((float)ImGui::GetTime(), IM_PI);
+      const float rstart = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+      const float radius1 = radius / 2.5f + thickness;
+      const float angle_offset = PI_2 / tris;
+
+      for (int i = 0; i <= tris; i++)
+      {
+        const float a = rstart + (i * angle_offset);
+        ImVec2 tri_centre(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1);
+        ImVec2 p1(tri_centre.x + ImCos(-a) * radius1, tri_centre.y + ImSin(-a) * radius1);
+        ImVec2 p2(tri_centre.x + ImCos(-a + PI_2 / 3.f) * radius1, tri_centre.y + ImSin(-a + PI_2 / 3.f) * radius1);
+        ImVec2 p3(tri_centre.x + ImCos(-a - PI_2 / 3.f) * radius1, tri_centre.y + ImSin(-a - PI_2 / 3.f) * radius1);
+        ImVec2 points[] = {p1, p2, p3};
+        window->DrawList->AddConvexPolyFilled(points, 3, color_alpha(color, 1.f));
+      }
+    }
+
+    inline void SpinnerRotateShapes(const char *label, float radius, float thickness, const ImColor &color, float speed, int shapes, int pnt)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime(), IM_PI);
+        const float rstart = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float radius1 = radius / 2.5f + thickness;
+        const float angle_offset = PI_2 / shapes;
+
+        std::vector<ImVec2> points(pnt);
+        const float begin_a = -IM_PI / ((pnt % 2 == 0) ? pnt : (pnt - 1));
+        for (int i = 0; i <= shapes; i++)
+        {
+            const float a = rstart + (i * angle_offset);
+            ImVec2 tri_centre(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1);
+            for (int pi = 0; pi < pnt; ++pi) {
+                points[pi] = {tri_centre.x + ImCos(begin_a + pi * PI_2 / pnt) * radius1, tri_centre.y + ImSin(begin_a + pi * PI_2 / pnt) * radius1};
+            }
+            window->DrawList->AddConvexPolyFilled(points.data(), pnt, color_alpha(color, 1.f));
+        }
+    }
+
+    inline void SpinnerSinSquares(const char *label, float radius, float thickness, const ImColor &color, float speed)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime(), IM_PI);
+        const float rstart = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float radius1 = radius / 2.5f + thickness;
+        const float angle_offset = PI_DIV_2;
+
+        std::vector<ImVec2> points(4);
+        for (int i = 0; i <= 4; i++)
+        {
+            const float a = rstart + (i * angle_offset);
+            const float begin_a = a - PI_DIV_2;
+            const float roff = ImMax(ImSin(start) - 0.5f, 0.f) * (radius * 0.4f);
+            ImVec2 tri_centre(centre.x + ImCos(a) * (radius1 + roff), centre.y + ImSin(a) * (radius1 + roff));
+            for (int pi = 0; pi < 4; ++pi) {
+                points[pi] = {tri_centre.x + ImCos(begin_a+ pi * PI_DIV_2) * radius1, tri_centre.y + ImSin(begin_a + pi * PI_DIV_2) * radius1};
+            }
+            window->DrawList->AddConvexPolyFilled(points.data(), 4, color_alpha(color, 1.f));
+        }
+    }
+
+    inline void SpinnerMoonLine(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bg = red, float speed = 2.8f, float angle = IM_PI)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime()* speed;
+      const float angle_offset = (angle * 0.5f) / num_segments;
+      const float th = thickness / num_segments;
+
+      window->DrawList->AddCircleFilled(centre, radius, bg, num_segments);
+
+      auto draw_gradient = [&] (const std::function<float (int)>& b, const std::function<float (int)>& e, const std::function<float (int)>& th) {
+        for (int i = 0; i < num_segments; i++)
+        {
+          window->DrawList->AddLine(ImVec2(centre.x + ImCos(start + b(i)) * radius, centre.y + ImSin(start + b(i)) * radius),
+                                    ImVec2(centre.x + ImCos(start + e(i)) * radius, centre.y + ImSin(start + e(i)) * radius),
+                                    color_alpha(color, 1.f),
+                                    th(i));
+        }
+
+      };
+
+      draw_gradient([&] (int i) { return (num_segments + i) * angle_offset; },
+                    [&] (int i) { return (num_segments + i + 1) * angle_offset; },
+                    [&] (int i) { return thickness - th * i; });
+
+      draw_gradient([&] (int i) { return (i) * angle_offset; },
+                    [&] (int i) { return (i + 1) * angle_offset; },
+                    [&] (int i) { return th * i; });
+
+      draw_gradient([&] (int i) { return (num_segments + i) * angle_offset; },
+                    [&] (int i) { return (num_segments + i + 1) * angle_offset; },
+                    [&] (int i) { return thickness - th * i; });
+
+      const float b_angle_offset = (PI_2 - angle) / num_segments; 
+      draw_gradient([&] (int i) { return num_segments * angle_offset * 2.f + (i * b_angle_offset); },
+                    [&] (int i) { return num_segments * angle_offset * 2.f + ((i + 1) * b_angle_offset); },
+                    [] (int) { return 1.f; });
+    }
+
+    inline void SpinnerCircleDrop(const char *label, float radius, float thickness, float thickness_drop, const ImColor &color = white, const ImColor &bg = half_white, float speed = 2.8f, float angle = IM_PI)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime() * speed;
+      const float bg_angle_offset = PI_2 / num_segments;
+      const float angle_offset = angle / num_segments;
+      const float th = thickness_drop / num_segments;
+      const float drop_radius_th = thickness_drop / num_segments;
+
+      for (int i = 0; i < num_segments; i++)
+      {
+        const float a = start + (i * angle_offset);
+        const float a1 = start + ((i + 1) * angle_offset);
+        const float s_drop_radius = radius - thickness / 2.f - (drop_radius_th * i);
+        window->DrawList->AddLine(ImVec2(centre.x + ImCos(a) * s_drop_radius, centre.y + ImSin(a) * s_drop_radius),
+                                  ImVec2(centre.x + ImCos(a1) * s_drop_radius, centre.y + ImSin(a1) * s_drop_radius),
+                                  color_alpha(color, 1.f),
+                                  th * 2.f * i);
+      }
+      const float ai_end = start + (num_segments * angle_offset);
+      const float f_drop_radius = radius - thickness / 2.f - thickness_drop;
+      ImVec2 circle_i_center{centre.x + ImCos(ai_end) * f_drop_radius, centre.y + ImSin(ai_end) * f_drop_radius};
+      window->DrawList->AddCircleFilled(circle_i_center, thickness_drop, color_alpha(color, 1.f), num_segments);
+
+      window->DrawList->PathClear();
+      for (int i = 0; i <= num_segments; i++)
+      {
+        const float a = (i * bg_angle_offset);
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius));
+      }
+      window->DrawList->PathStroke(color_alpha(bg, 1.f), false, thickness);
+    }
+
+    inline void SpinnerSurroundedIndicator(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bg = half_white, float speed = 2.8f)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float lerp_koeff = (ImSin((float)ImGui::GetTime() * speed) + 1.f) * 0.5f;
+      window->DrawList->AddCircleFilled(centre, thickness, color_alpha(bg, 1.f), num_segments);
+      window->DrawList->AddCircleFilled(centre, thickness, color_alpha(color, ImMax(0.1f, ImMin(lerp_koeff, 1.f))), num_segments);
+
+      auto PathArc = [&] (const ImColor& c, float th) {
+        window->DrawList->PathClear();
+        const float bg_angle_offset = PI_2 / num_segments;
+        for (int i = 0; i <= num_segments; i++)
+          window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(i * bg_angle_offset) * radius, centre.y + ImSin(i * bg_angle_offset) * radius));
+        window->DrawList->PathStroke(color_alpha(c, 1.f), false, th);
+      };
+      
+      lerp_koeff = (ImSin((float)ImGui::GetTime() * speed * 1.6f) + 1.f) * 0.5f;
+      PathArc(bg, thickness);
+      PathArc(color_alpha(color, 1.f - ImMax(0.1f, ImMin(lerp_koeff, 1.f))), thickness);
+    }
+
+    inline void SpinnerWifiIndicator(const char *label, float radius, float thickness, const ImColor &color = red, const ImColor &bg = half_white, float speed = 2.8f, float cangle = 0.f, int dots = 3)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        float lerp_koeff = (ImSin((float)ImGui::GetTime() * speed) + 1.f) * 0.5f;
+        float start_ang = -cangle - PI_DIV_4 - PI_DIV_2;
+        ImVec2 pc(centre.x + ImSin(cangle) * radius, centre.y + ImCos(cangle) * radius);
+        window->DrawList->AddCircleFilled(pc, thickness, bg, num_segments);
+        window->DrawList->AddCircleFilled(pc, thickness, color_alpha(color, ImMax(0.1f, ImMin(lerp_koeff, 1.f))), num_segments);
+
+        auto PathArc = [&] (float as, const ImColor& c, float th, float r) {
+            window->DrawList->PathClear();
+            const float bg_angle_offset = PI_DIV(2) / num_segments;
+            for (int i = 0; i <= num_segments; i++)
+                window->DrawList->PathLineTo(ImVec2(pc.x + ImCos(as + i * bg_angle_offset) * r, pc.y + ImSin(as + i * bg_angle_offset) * r));
+            window->DrawList->PathStroke(color_alpha(c, 1.f), false, th);
+        };
+
+        const float interval = (size.x * 0.7f) / dots;
+        for (int i = 0; i < dots; ++i) {
+            float r = 1.5f * (i + 1) * interval;
+            lerp_koeff = (ImSin((float)ImGui::GetTime() * speed - (i+1) * (IM_PI / dots)) + 1.f) * 0.5f;
+            PathArc(start_ang, bg, thickness, r);
+            PathArc(start_ang, color_alpha(color, ImMax(0.1f, ImMin(lerp_koeff, 1.f))), thickness, r);
+        }
+    }
+
+    inline void SpinnerTrianglesSelector(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bg = half_white, float speed = 2.8f, size_t bars = 8)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float lerp_koeff = (ImSin((float)ImGui::GetTime() * speed) + 1.f) * 0.5f;
+      ImColor c = color_alpha(color, ImMax(0.1f, ImMin(lerp_koeff, 1.f)));
+      float dr = radius - thickness - 3;
+      window->DrawList->AddCircleFilled(centre, dr, bg, num_segments);
+      window->DrawList->AddCircleFilled(centre, dr, c, num_segments);
+
+      // Render
+      float start = (float)ImGui::GetTime() * speed;
+      float astart = ImFmod(start, PI_2 / bars);
+      start -= astart;
+      const float angle_offset = PI_2 / bars;
+      const float angle_offset_t = angle_offset * 0.3f;
+      bars = ImMin<size_t>(bars, 32);
+
+      const float rmin = radius - thickness;
+      auto get_points = [&] (float left, float right) -> std::array<ImVec2, 4> {
+        return {
+          ImVec2(centre.x + ImCos(left) * rmin, centre.y + ImSin(left) * rmin),
+          ImVec2(centre.x + ImCos(left) * radius, centre.y + ImSin(left) * radius),
+          ImVec2(centre.x + ImCos(right) * radius, centre.y + ImSin(right) * radius),
+          ImVec2(centre.x + ImCos(right) * rmin, centre.y + ImSin(right) * rmin)
+        };
+      };
+
+      auto draw_sectors = [&] (float s, const std::function<ImU32 (size_t)>& color_func) {
+        for (size_t i = 0; i <= bars; i++) {
+          float left = s + (i * angle_offset) - angle_offset_t;
+          float right = s + (i * angle_offset) + angle_offset_t;
+          auto points = get_points(left, right);
+          window->DrawList->AddConvexPolyFilled(points.data(), 4, color_func(i));
+        }
+      };
+
+      draw_sectors(0, [&] (size_t) { return color_alpha(bg, 0.1f); });
+      draw_sectors(start, [&] (size_t i) { return color_alpha(bg, (i / (float)bars) - 0.5f); });
+    }
+
+    using LeafColor = ImColor (int);
+    inline void SpinnerCamera(const char *label, float radius, float thickness, LeafColor *leaf_color, float speed = 2.8f, size_t bars = 8)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime() * speed;
+      const float angle_offset = PI_2 / bars;
+      const float angle_offset_t = angle_offset * 0.3f;
+      bars = ImMin<size_t>(bars, 32);
+
+      const float rmin = radius - thickness - 1;
+      auto get_points = [&] (float left, float right) -> std::array<ImVec2, 4> {
+        return {
+          ImVec2(centre.x + ImCos(left - 0.1f) * radius, centre.y + ImSin(left - 0.1f) * radius),
+          ImVec2(centre.x + ImCos(right + 0.15f) * radius, centre.y + ImSin(right + 0.15f) * radius),
+          ImVec2(centre.x + ImCos(right - 0.91f) * rmin, centre.y + ImSin(right - 0.91f) * rmin)
+        };
+      };
+
+      auto draw_sectors = [&] (float s, const std::function<ImU32 (int)>& color_func) {
+        for (size_t i = 0; i <= bars; i++) {
+          float left = s + (i * angle_offset) - angle_offset_t;
+          float right = s + (i * angle_offset) + angle_offset_t;
+          auto points = get_points(left, right);
+          window->DrawList->AddConvexPolyFilled(points.data(), 3, color_alpha(color_func((int)i), 1.f));
+        }
+      };
+
+      draw_sectors(start, leaf_color);
+    }
+
+    inline void SpinnerFlowingGradient(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bg = red, float speed = 2.8f, float angle = IM_PI)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime()* speed;
+      const float angle_offset = (angle * 0.5f) / num_segments;
+      const float bg_angle_offset = (PI_2) / num_segments;
+      const float th = thickness / num_segments;
+
+      for (size_t i = 0; i <= num_segments; i++)
+      {
+        const float a = (i * bg_angle_offset);
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius));
+      }
+      window->DrawList->PathStroke(bg, false, thickness);
+
+      auto draw_gradient = [&] (const std::function<float (size_t)>& b, const std::function<float (size_t)>& e, const std::function<ImU32 (size_t)>& c) {
+        for (size_t i = 0; i < num_segments; i++)
+        {
+          window->DrawList->AddLine(ImVec2(centre.x + ImCos(start + b(i)) * radius, centre.y + ImSin(start + b(i)) * radius),
+                                    ImVec2(centre.x + ImCos(start + e(i)) * radius, centre.y + ImSin(start + e(i)) * radius),
+                                    c(i),
+                                    thickness);
+        }
+      };
+
+      draw_gradient([&] (size_t i) { return (i) * angle_offset; },
+                    [&] (size_t i) { return (i + 1) * angle_offset; },
+                    [&] (size_t i) { return color_alpha(color, (i / (float)num_segments)); });
+
+      draw_gradient([&] (size_t i) { return (num_segments + i) * angle_offset; },
+                    [&] (size_t i) { return (num_segments + i + 1) * angle_offset; },
+                    [&] (size_t i) { return color_alpha(color, 1.f - (i / (float)num_segments)); });
+    }
+
+    inline void SpinnerRotateSegments(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4, size_t layers = 1)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime()* speed;
+      const float arc_angle = PI_2 / (float)arcs;
+      const float angle_offset = arc_angle / num_segments;
+      float r = radius;
+      float reverse = 1.f;
+      for (size_t layer = 0; layer < layers; layer++)
+      {
+        for (size_t arc_num = 0; arc_num < arcs; ++arc_num)
+        {
+          window->DrawList->PathClear();
+          for (size_t i = 2; i <= num_segments - 2; i++)
+          {
+            const float a = start * (1 + 0.1f * layer) + arc_angle * arc_num + (i * angle_offset);
+            window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a * reverse) * r, centre.y + ImSin(a * reverse) * r));
+          }
+          window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+        }
+
+        r -= (thickness + 1);
+        reverse *= -1.f;
+      }
+    }
+
+    inline void SpinnerLemniscate(const char* label, float radius, float thickness, const ImColor& color = white, float speed = 2.8f, float angle = IM_PI / 2.0f)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime() * speed;
+      const float a = radius;
+      const float t = start;
+      const float step = angle / num_segments;
+      const float th = thickness / num_segments;
+
+      auto get_coord = [&](float const& a, float const& t) -> std::pair<float, float> {
+          return std::make_pair((a * ImCos(t)) / (1 + (powf(ImSin(t), 2.0f))), (a * ImSin(t) * ImCos(t)) / (1 + (powf(ImSin(t), 2.0f))));
+      };
+
+      for (size_t i = 0; i < num_segments; i++)
+      {
+          const auto xy0 = get_coord(a, start + (i * step));
+          const auto xy1 = get_coord(a, start + ((i + 1) * step));
+      
+          window->DrawList->AddLine(ImVec2(centre.x + xy0.first, centre.y + xy0.second),
+              ImVec2(centre.x + xy1.first, centre.y + xy1.second),
+              color_alpha(color, 1.f),
+              th * i);
+      }
+    }
+
+    inline void SpinnerRotateGear(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t pins = 12)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime()* speed;
+      const float bg_angle_offset = PI_2 / num_segments;
+      const float bg_radius = radius - thickness;
+
+      window->DrawList->PathClear();
+      for (size_t i = 0; i <= num_segments; i++)
+      {
+        const float a = (i * bg_angle_offset);
+        window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * bg_radius, centre.y + ImSin(a) * bg_radius));
+      }
+      window->DrawList->PathStroke(color_alpha(color, 1.f), false, bg_radius / 2);
+
+      const float rmin = bg_radius;
+      const float rmax = radius;
+      const float pin_angle_offset = PI_2 / pins;
+      for (size_t i = 0; i <= pins; i++)
+      {
+        float a = start + (i * pin_angle_offset);
+        window->DrawList->AddLine(ImVec2(centre.x + ImCos(a) * rmin, centre.y + ImSin(a) * rmin),
+                                  ImVec2(centre.x + ImCos(a) * rmax, centre.y + ImSin(a) * rmax),
+                                  color_alpha(color, 1.f), thickness);
+      }
+    }
+
+    inline void SpinnerRotateWheel(const char *label, float radius, float thickness, const ImColor &bg_color = white, const ImColor &color = white, float speed = 2.8f, size_t pins = 12)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        const float bg_radius = radius - thickness;
+        const float line_th = ImMax(radius / 8.f, 3.f);
+
+        auto draw_circle = [window, num_segments, centre] (float r, const ImColor &c, float th) {
+            const float bg_angle_offset = PI_2 / num_segments;
+            window->DrawList->PathClear();
+            for (size_t i = 0; i <= num_segments; i++)
+            {
+                const float a = (i * bg_angle_offset);
+                window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * r, centre.y + ImSin(a) * r));
+            }
+            window->DrawList->PathStroke(color_alpha(c, 1.f), false, th);
+        };
+
+        auto draw_pins = [window, centre, pins, start] (float rmin, float rmax, const ImColor &c, float th) {
+            const float pin_angle_offset = PI_2 / pins;
+            for (size_t i = 0; i <= pins; i++)             {
+                float a = start + (i * pin_angle_offset);
+                window->DrawList->AddLine(ImVec2(centre.x + ImCos(a) * rmin, centre.y + ImSin(a) * rmin),
+                                          ImVec2(centre.x + ImCos(a) * rmax, centre.y + ImSin(a) * rmax),
+                                          color_alpha(c, 1.f), th);
+            }
+        };
+
+        draw_circle(bg_radius, bg_color, line_th);
+        draw_pins(bg_radius, radius, bg_color, line_th);
+        draw_circle(radius, color, line_th);
+    }
+
+    inline void SpinnerAtom(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int elipses = 3)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime()* speed;
+      elipses = std::min<int>(elipses, 3);
+
+      auto draw_rotated_ellipse = [&] (float alpha, float start) {
+        alpha = ImFmod(alpha, IM_PI);
+        float a = radius;
+        float b = radius / 2.f; 
+
+        window->DrawList->PathClear();
+        for (int i = 0; i < num_segments; ++i) {
+          float anga = (i * (PI_2 / (num_segments - 1)));
+
+          float xx = a * ImCos(anga) * ImCos(alpha) + b * ImSin(anga) * ImSin(alpha) + centre.x;
+          float yy = b * ImSin(anga) * ImCos(alpha) - a * ImCos(anga) * ImSin(alpha) + centre.y;
+          window->DrawList->PathLineTo({xx, yy});
+        }
+        window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+
+        float anga = ImFmod(start, PI_2);
+        float x = a * ImCos(anga) * ImCos(alpha) + b * ImSin(anga) * ImSin(alpha) + centre.x;
+        float y = b * ImSin(anga) * ImCos(alpha) - a * ImCos(anga) * ImSin(alpha) + centre.y;
+        return ImVec2{x, y};
+      };
+
+      ImVec2 ppos[3];
+      for (int i = 0; i < elipses; ++i) {
+        ppos[i % 3] = draw_rotated_ellipse((IM_PI * (float)i/ elipses), start * (1.f + 0.1f * i));
+      }
+
+      ImColor pcolors[3] = {ImColor(255, 0, 0), ImColor(0, 255, 0), ImColor(0, 0, 255)};
+      for (int i = 0; i < elipses; ++i) {
+        window->DrawList->AddCircleFilled(ppos[i], thickness * 2, color_alpha(pcolors[i], 1.f), int(num_segments / 3.f));
+      }
+    }
+
+    inline void SpinnerPatternRings(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int elipses = 3)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime()* speed;
+        elipses = std::max<int>(elipses, 1);
+
+        auto draw_rotated_ellipse = [&] (float alpha, float tr, float y) {
+            alpha = ImFmod(alpha, IM_PI);
+            float a = radius;
+            float b = radius / 2.f; 
+
+            const float bg_angle_offset = PI_2 / (num_segments - 1);
+            ImColor c = color_alpha(color, tr);
+            window->DrawList->PathClear();
+            for (int i = 0; i < num_segments; ++i) {
+                float anga = (i * bg_angle_offset);
+                float xx = a * ImCos(anga) * ImCos(alpha) + b * ImSin(anga) * ImSin(alpha) + centre.x;
+                float yy = b * ImSin(anga) * ImCos(alpha) - a * ImCos(anga) * ImSin(alpha) + centre.y + y;
+                window->DrawList->PathLineTo({xx, yy});
+            }
+            window->DrawList->PathStroke(c, false, thickness);
+        };
+
+        for (int i = 0; i < elipses; ++i)
+        {
+            const float h = (0.5f * ImSin(start + (IM_PI / elipses) * i));
+            draw_rotated_ellipse(0.f, 0.1f + (0.9f / elipses) * i, radius * h);
+        }
+    }
+
+    inline void SpinnerPatternEclipse(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int elipses = 3, float delta_a = 2.f, float delta_y = 0.f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime()* speed;
+        elipses = std::max<int>(elipses, 1);
+
+        auto draw_rotated_ellipse = [&] (const ImVec2 &pp, float alpha, float tr, float r, float x, float y) {
+            alpha = ImFmod(alpha, IM_PI);
+            float a = r;
+            float b = r / delta_a; 
+
+            ImColor c = color_alpha(color, tr);
+            window->DrawList->PathClear();
+            for (int i = 0; i < num_segments; ++i) {
+                float anga = (i *  PI_2 / (num_segments - 1));
+                float xx = a * ImCos(anga) * ImCos(alpha) + b * ImSin(anga) * ImSin(alpha) + pp.x + x;
+                float yy = b * ImSin(anga) * ImCos(alpha) - a * ImCos(anga) * ImSin(alpha) + pp.y + y;
+                window->DrawList->PathLineTo({xx, yy});
+            }
+            window->DrawList->PathStroke(c, false, thickness);
+        };
+
+        for (int i = 0; i < elipses; ++i)
+        {
+            const float rkoeff = (0.5f + 0.5f * ImSin(start + (IM_PI / elipses) * i));
+            const float h = ((1.f / elipses) * (i+1));
+            const float anga = start + (i *  PI_DIV_2 / elipses);
+            const float yoff = ((1.f / elipses) * i) * delta_y;
+            float a = (radius * (1.f - h));
+            float b = (radius * (1.f - h)) / delta_a; 
+            float xx = a * ImCos(anga) * ImCos(0) + b * ImSin(anga) * ImSin(0) + centre.x;
+            float yy = b * ImSin(anga) * ImCos(0) - a * ImCos(anga) * ImSin(0) + centre.y;
+            draw_rotated_ellipse(ImVec2(xx, yy), 0.f, 0.3f + (0.7f / elipses) * i, radius * h, 0.f, yoff * radius);
+        }
+    }
+
+    inline void SpinnerPatternSphere(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int elipses = 3)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed * 3.f, size.y);
+        elipses = std::max<int>(elipses, 1);
+
+        auto draw_rotated_ellipse = [&] (float alpha, float tr, float y, float r) {
+            alpha = ImFmod(alpha, IM_PI);
+            float a = r;
+            float b = r / 4.f; 
+
+            ImColor c = color_alpha(color, tr);
+            window->DrawList->PathClear();
+            for (int i = 0; i < num_segments; ++i) {
+                float anga = (i * PI_2 / (num_segments - 1));
+                float xx = a * ImCos(anga) * ImCos(alpha) + b * ImSin(anga) * ImSin(alpha) + centre.x;
+                float yy = b * ImSin(anga) * ImCos(alpha) - a * ImCos(anga) * ImSin(alpha) + pos.y + y;
+                window->DrawList->PathLineTo({xx, yy});
+            }
+            window->DrawList->PathStroke(c, false, thickness);
+        };
+
+        float offset = 0;
+        float half_size = size.y * 0.5f;
+        for (size_t i = 0; i < elipses; i++)
+        {
+            offset = ImFmod(start + i * (size.y / elipses), size.y);
+            const float th = (offset > half_size)
+                                ? 1.f - (offset - half_size) / half_size
+                                : offset / half_size;
+            draw_rotated_ellipse(0.f, th, offset, ImSin(offset / size.y * IM_PI) * radius);
+        }
+    }
+
+    inline void SpinnerRingSynchronous(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int elipses = 3)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+       
+        num_segments *= 4;
+        const float aoffset = ImFmod((float)ImGui::GetTime(), PI_2);
+        const float bofsset = (aoffset > IM_PI) ? IM_PI : aoffset;
+        const float angle_offset = PI_2 / num_segments;
+        float ared_min = 0, ared = 0;
+        if (aoffset > IM_PI)
+            ared_min = aoffset - IM_PI;
+
+        auto draw_ellipse = [&] (float alpha, float y, float r) {
+            window->DrawList->PathClear();
+            for (size_t i = 0; i <= num_segments + 1; i++)
+            {
+                ared = start + (i * angle_offset);
+
+                if (i * angle_offset < ared_min * 2)
+                    continue;
+
+                if (i * angle_offset > bofsset * 2.f)
+                    break;
+
+                float a = r;
+                float b = r * 0.25f;
+
+                const float xx = a * ImCos(ared) * ImCos(alpha) + b * ImSin(ared) * ImSin(alpha) + centre.x;
+                const float yy = b * ImSin(ared) * ImCos(alpha) - a * ImCos(ared) * ImSin(alpha) + pos.y + y;
+                window->DrawList->PathLineTo(ImVec2(xx, yy));
+            }
+            window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+        };
+
+        for (int i = 0; i < elipses; ++i)
+        {
+            float y = i * ((float)(size.y * 0.7f) / (float)elipses) + (size.y * 0.15f);
+            draw_ellipse(0, y, radius * ImSin((i + 1) * (IM_PI / (elipses + 1))));
+        }
+    }
+
+    inline void SpinnerRingWatermarks(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int elipses = 3)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        num_segments *= 4;
+
+        const float angle_offset = PI_2 / num_segments;
+        auto draw_ellipse = [&] (float s, float alpha, float x, float y, float r) {
+            const float aoffset = ImFmod((float)s, PI_2);
+            const float bofsset = (aoffset > IM_PI) ? IM_PI : aoffset;
+            float ared_min = 0, ared = 0;
+            if (aoffset > IM_PI)
+                ared_min = aoffset - IM_PI;
+
+            window->DrawList->PathClear();
+            for (size_t i = 0; i <= num_segments + 1; i++)
+            {
+                ared = s + (i * angle_offset);
+
+                if (i * angle_offset < ared_min * 2)
+                    continue;
+
+                if (i * angle_offset > bofsset * 2.f)
+                    break;
+
+                float a = r;
+                float b = r * 0.25f;
+
+                const float xx = a * ImCos(ared) * ImCos(alpha) + b * ImSin(ared) * ImSin(alpha) + centre.x + x;
+                const float yy = b * ImSin(ared) * ImCos(alpha) - a * ImCos(ared) * ImSin(alpha) + pos.y + y;
+                window->DrawList->PathLineTo(ImVec2(xx, yy));
+            }
+            window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+        };
+
+        for (int i = 0; i < elipses; ++i)
+        {
+            float y = i * ((float)(size.y * 0.7f) / (float)elipses) + (size.y * 0.15f);
+            float x = -i * (radius / elipses);
+            draw_ellipse(start + (i * IM_PI / (elipses * 2)), -PI_DIV_4, x, y, radius);
+        }
+    }
+
+    inline void SpinnerRotatedAtom(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int elipses = 3)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = (float)ImGui::GetTime()* speed;
+      auto draw_rotated_ellipse = [&] (float alpha) {
+        std::array<ImVec2, 36> pts;
+
+        alpha = ImFmod(alpha, IM_PI);
+        float a = radius;
+        float b = radius / 2.f; 
+
+        const float bg_angle_offset = PI_2 / num_segments;
+        for (size_t i = 0; i < num_segments; ++i) {
+          float anga = (i * bg_angle_offset);
+
+          pts[i].x = a * ImCos(anga) * ImCos(alpha) + b * ImSin(anga) * ImSin(alpha) + centre.x;
+          pts[i].y = b * ImSin(anga) * ImCos(alpha) - a * ImCos(anga) * ImSin(alpha) + centre.y;
+        }
+        for (size_t i = 1; i < num_segments; ++i) {
+          window->DrawList->AddLine(pts[i-1], pts[i], color_alpha(color, 1.f), thickness);
+        }
+        window->DrawList->AddLine(pts[num_segments-1], pts[0], color_alpha(color, 1.f), thickness);
+      };
+
+      for (int i = 0; i < elipses; ++i) {
+        draw_rotated_ellipse(start + (IM_PI * (float)i/ elipses));
+      }
+    }
+
+    inline void SpinnerRainbowBalls(const char *label, float radius, float thickness, const ImColor &color, float speed, int balls = 5)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const float start = ImFmod((float)ImGui::GetTime() * speed * 3.f, IM_PI);
+      const float colorback = 0.3f + 0.2f * ImSin((float)ImGui::GetTime() * speed);
+      const float rstart = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+      const float radius1 = (0.8f + 0.2f * ImSin(start)) * radius;
+      const float angle_offset = PI_2 / balls;
+      const bool rainbow = ((ImU32)color.Value.w) == 0;
+
+      float out_h, out_s, out_v;
+      ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+      for (int i = 0; i <= balls; i++)
+      {
+        const float a = rstart + (i * angle_offset);
+        ImColor c = rainbow ? ImColor::HSV(out_h + i * (1.f / balls) + colorback, out_s, out_v) : color;
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1), thickness, color_alpha(c, 1.f), num_segments);
+      }
+    }
+
+    inline void SpinnerRainbowShot(const char *label, float radius, float thickness, const ImColor &color, float speed, int balls = 5)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed * 3.f, PI_2);
+        const float colorback = 0.3f + 0.2f * ImSin((float)ImGui::GetTime() * speed);
+        const float rstart = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float angle_offset = PI_2 / balls;
+        const bool rainbow = ((ImU32)color.Value.w) == 0;
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (int i = 0; i <= balls; i++)
+        {
+            float centera = start - PI_DIV_2 + (i * angle_offset);
+            float rmul = ImMax(0.2f, 1.f - ImSin(centera));
+            const float radius1 = ImMin(radius * rmul, radius);
+            const float a = (i * angle_offset);
+            ImColor c = rainbow ? ImColor::HSV(out_h + i * (1.f / balls) + colorback, out_s, out_v) : color;
+            window->DrawList->AddLine(centre, ImVec2(centre.x + ImCos(a) * radius1, centre.y + ImSin(a) * radius1), color_alpha(c, 1.f), thickness);
+        }
+    }
+
+    inline void SpinnerSpiral(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        float a = radius / num_segments;
+        float b = a;
+
+        ImVec2 last = centre;
+        for (size_t arc_num = 0; arc_num < (num_segments * arcs); ++arc_num)
+        {
+            float angle = (PI_2 / num_segments) * arc_num;
+            float x = centre.x + (a + b * angle) * ImCos(start + angle);
+            float y = centre.y + (a + b * angle) * ImSin(start + angle);
+
+            window->DrawList->AddLine(last, ImVec2(x, y), color_alpha(color, 1.f), thickness);
+            last = ImVec2(x, y);
+        }
+    }
+
+    inline void SpinnerSpiralEye(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        float a = (radius * 3.f) / num_segments;
+        float b = a;
+        num_segments *= 4;
+
+        auto half_eye = [&] (float side) {
+            for (size_t arc_num = 0; arc_num < num_segments; ++arc_num)         {
+                float angle = (PI_2 / num_segments) * arc_num;
+                float x = centre.x + (a + b * angle) * ImCos((start + angle) * side);
+                float y = centre.y + (a + b * angle) * ImSin((start + angle) * side);
+
+                window->DrawList->AddCircleFilled(ImVec2(x, y), thickness, color_alpha(color, 1.f));
+            }
+        };
+
+        half_eye(1.f);
+        half_eye(-1.f);
+    }
+
+    inline void SpinnerBarChartSine(const char *label, float radius, float thickness, const ImColor &color, float speed, int bars = 5, int mode = 0)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const ImGuiStyle &style = GImGui->Style;
+      const float nextItemKoeff = 1.5f;
+      const float yOffsetKoeftt = 0.8f;
+      const float heightSpeed = 0.8f;
+
+      const float start = (float)ImGui::GetTime() * speed;
+      const float offset = IM_PI / bars;
+      for (int i = 0; i < bars; i++)
+      {
+        const float angle = ImMax(PI_DIV_2, (1.f - i/(float)bars) * IM_PI);
+        float a = start + (IM_PI - i * offset);
+        ImColor c = color_alpha(color, ImMax(0.1f, ImSin(a * heightSpeed)));
+        float h = mode ? ImSin(a) * size.y / 2.f
+                       : (0.6f + 0.4f * c.Value.w) * size.y;
+        float halfs = mode ? 0 : size.y / 2.f;
+        window->DrawList->AddRectFilled(ImVec2(pos.x + style.FramePadding.x + i * (thickness * nextItemKoeff) - thickness / 2, centre.y + halfs),
+                                        ImVec2(pos.x + style.FramePadding.x + i * (thickness * nextItemKoeff) + thickness / 2, centre.y + halfs - h * yOffsetKoeftt),
+                                        c);
+      }
+    }
+
+    inline void SpinnerBarChartAdvSine(const char *label, float radius, float thickness, const ImColor &color, float speed, int mode = 0)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 1.5f;
+        const float start = (float)ImGui::GetTime() * speed;
+        const int bars = (int)(radius * 2 / thickness);
+        const float offset = PI_DIV_2 / bars;
+        for (int i = 0; i < bars; i++)
+        {
+            float a = start + (PI_DIV_2 - i * offset);
+            float halfsx = thickness * ImSin(a);
+            float halfsy = (ImMax(0.1f, ImSin(a) + 1.f)) * radius * 0.5f;
+            window->DrawList->AddRectFilled(ImVec2(pos.x + i * (thickness * nextItemKoeff) - thickness / 2 + halfsx, centre.y + halfsy),
+                                            ImVec2(pos.x + i * (thickness * nextItemKoeff) + thickness / 2 + halfsx, centre.y - halfsy),
+                                            color);
+        }
+    }
+
+    inline void SpinnerBarChartAdvSineFade(const char *label, float radius, float thickness, const ImColor &color, float speed, int mode = 0)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        const int bars = (int)(radius * 2 / thickness);
+        const float offset = PI_DIV_2 / bars;
+        for (int i = 0; i < bars; i++)
+        {
+            float a = start - i * offset;
+            float halfsy = ImMax(0.1f, ImCos(a) + 1.f) * radius * 0.5f;
+            window->DrawList->AddRectFilled(ImVec2(pos.x + i * thickness - thickness / 2, centre.y + halfsy),
+                                            ImVec2(pos.x + i * thickness + thickness / 2, centre.y - halfsy),
+                                            color_alpha(color, ImMax(0.1f, halfsy / radius)));
+        }
+    }
+
+    inline void SpinnerBarChartRainbow(const char *label, float radius, float thickness, const ImColor &color, float speed, int bars = 5)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const ImGuiStyle &style = GImGui->Style;
+      const float nextItemKoeff = 1.5f;
+      const float yOffsetKoeftt = 0.8f;
+
+      const float start = (float)ImGui::GetTime() * speed;
+      const float hspeed = 0.1f + ImSin((float)ImGui::GetTime() * 0.1f) * 0.05f;
+      constexpr float rkoeff[6] = {4.f, 13.f, 3.4f, 8.7f, 25.f, 11.f};
+      float out_h, out_s, out_v;
+      ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+      for (int i = 0; i < bars; i++)
+      {
+        ImColor c = ImColor::HSV(out_h + i * 0.1f, out_s, out_v);
+        float h = (0.6f + 0.4f * ImSin(start + (1.f + rkoeff[i % 6] * i * hspeed)) ) * size.y;
+        window->DrawList->AddRectFilled(ImVec2(pos.x + style.FramePadding.x + i * (thickness * nextItemKoeff) - thickness / 2, centre.y + size.y / 2.f),
+                                        ImVec2(pos.x + style.FramePadding.x + i * (thickness * nextItemKoeff) + thickness / 2, centre.y + size.y / 2.f - h * yOffsetKoeftt),
+                                        color_alpha(c, 1.f));
+      }
+    }
+
+    inline void SpinnerBlocks(const char *label, float radius, float thickness, const ImColor &bg, const ImColor &color, float speed)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      ImVec2 lt{centre.x - radius, centre.y - radius};
+      const float offset_block = radius * 2.f / 3.f;
+
+      int start = (int)ImFmod((float)ImGui::GetTime() * speed, 8.f);
+
+      const ImVec2ih poses[] = {{0, 0}, {1, 0}, {2, 0}, {2, 1}, {2, 2}, {1, 2}, {0, 2}, {0, 1}};
+
+      int ti = 0;
+      for (const auto &rpos: poses)
+      {
+          const ImColor &c = (ti == start) ? color : bg;
+          window->DrawList->AddRectFilled(ImVec2(lt.x + rpos.x * (offset_block), lt.y + rpos.y * offset_block),
+                                          ImVec2(lt.x + rpos.x * (offset_block) + thickness, lt.y + rpos.y * offset_block + thickness),
+                                          color_alpha(c, 1.f));
+          ti++;
+      }
+    }
+
+    inline void SpinnerTwinBlocks(const char *label, float radius, float thickness, const ImColor &bg, const ImColor &color, float speed)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float offset_block = radius * 2.f / 3.f;
+        ImVec2 lt{centre.x - radius - offset_block / 2.f, centre.y - radius - offset_block / 2.f};
+
+        int start = (int)ImFmod((float)ImGui::GetTime() * speed, 8.f);
+        const ImVec2ih poses[] = {{0, 0}, {1, 0}, {2, 0}, {2, 1}, {2, 2}, {1, 2}, {0, 2}, {0, 1}};
+
+        int ti = 0;
+        for (const auto &rpos: poses)
+        {
+            const ImColor &c = (ti == start) ? color : bg;
+            window->DrawList->AddRectFilled(ImVec2(lt.x + rpos.x * (offset_block), lt.y + rpos.y * offset_block),
+                                            ImVec2(lt.x + rpos.x * (offset_block) + thickness, lt.y + rpos.y * offset_block + thickness),
+                                            color_alpha(c, 1.f));
+            ti++;
+        }
+
+        lt = ImVec2{centre.x - radius + offset_block / 2.f, centre.y - radius + offset_block / 2.f};
+        ti = (int)std::size(poses) - 1;
+        start = (int)ImFmod((float)ImGui::GetTime() * speed * 1.1f, 8.f);
+        for (const auto &rpos: poses)
+        {
+            const ImColor &c = (ti == start) ? color : bg;
+            window->DrawList->AddRectFilled(ImVec2(lt.x + rpos.x * (offset_block), lt.y + rpos.y * offset_block),
+                                            ImVec2(lt.x + rpos.x * (offset_block) + thickness, lt.y + rpos.y * offset_block + thickness),
+                                            color_alpha(c, 1.f));
+            ti--;
+        }
+    }
+
+    inline void SpinnerSquareRandomDots(const char *label, float radius, float thickness, const ImColor &bg, const ImColor &color, float speed)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float offset_block = radius * 2.f / 3.f;
+        ImVec2 lt{centre.x - offset_block, centre.y - offset_block};
+
+        int start = (int)ImFmod((float)ImGui::GetTime() * speed, 9.f);
+
+        ImGuiStorage* storage = window->DC.StateStorage;
+        const ImGuiID vtimeId = window->GetID("##vtime");
+        const ImGuiID vvald = window->GetID("##vval");
+        int vtime = storage->GetInt(vtimeId, 0);
+        int vval = storage->GetInt(vvald, 0);
+
+        if (vtime != start) {
+            vval = rand() % 9;
+            storage->SetInt(vvald, vval);
+            storage->SetInt(vtimeId, start);
+        }
+
+        const ImVec2ih poses[] = {{0, 0}, {1, 0}, {2, 0}, {2, 1}, {2, 2}, {1, 2}, {0, 2}, {0, 1}, {1, 1}};
+        int ti = 0;
+        for (const auto &rpos: poses)
+        {
+            const ImColor &c = (ti == vval) ? color : bg;
+            window->DrawList->AddCircleFilled(ImVec2(lt.x + rpos.x * (offset_block), lt.y + rpos.y * offset_block), thickness,
+                                              color_alpha(c, 1.f));
+            ti++;
+        }
+    }
+
+    inline void SpinnerScaleBlocks(const char *label, float radius, float thickness, const ImColor &color, float speed, int mode = 0)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      ImVec2 lt{centre.x - radius, centre.y - radius};
+      const float offset_block = radius * 2.f / 3.f;
+
+      const ImVec2ih poses[] = {{0, 0}, {1, 0}, {2, 0}, {0, 1}, {1, 1}, {2, 1}, {0, 2}, {1, 2}, {2, 2}};
+      constexpr float rkoeff[9] = {0.1f, 0.15f, 0.17f, 0.25f, 0.6f, 0.15f, 0.1f, 0.12f, 0.22f};
+
+      int ti = 0;
+      float out_h, out_s, out_v;
+      ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+      for (const auto &rpos: poses)
+      {
+        ImColor c = ImColor::HSV(out_h + ti * 0.1f, out_s, out_v);
+        if (mode) {
+            float h = (0.1f + 0.4f * ImSin((float)ImGui::GetTime() * (speed * rkoeff[ti % 9])));
+            window->DrawList->AddCircleFilled(ImVec2(lt.x + rpos.x * (offset_block), lt.y + rpos.y * offset_block), std::max<float>(1.f, h * thickness),
+                                              color_alpha(c, 1.f));
+        } else {
+            float h = (0.8f + 0.4f * ImSin((float)ImGui::GetTime() * (speed * rkoeff[ti % 9])));
+            window->DrawList->AddRectFilled(ImVec2(lt.x + rpos.x * (offset_block), lt.y + rpos.y * offset_block),
+                                           ImVec2(lt.x + rpos.x * (offset_block) + h * thickness, lt.y + rpos.y * offset_block + h * thickness),
+                                           color_alpha(c, 1.f));
+        }
+        ti++;
+      }
+    }
+
+    inline void SpinnerScaleSquares(const char *label, float radius, float thikness, const ImColor &color, float speed)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        ImVec2 lt{centre.x - radius, centre.y - radius};
+        const float offset_block = radius * 2.f / 3.f;
+        const float hside = (thikness / 2.f);
+
+        const ImVec2ih poses[] = {{0, 0}, {1, 0}, {0, 1}, {2, 0}, {1, 1}, {0, 2}, {2, 1}, {1, 2}, {2, 2}};
+        const float offsets[] =  {0.f,    0.8f,   0.8f,   1.6f,   1.6f,   1.6f,   2.4f,   2.4f,   3.2f};
+
+        int ti = 0;
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (const auto &rpos: poses)
+        {
+            const ImColor c = ImColor::HSV(out_h + offsets[ti], out_s, out_v);
+            const float strict = (0.5f + 0.5f * ImSin((float)-ImGui::GetTime() * speed + offsets[ti % 9]));
+            const float side = ImClamp<float>(strict + 0.1f, 0.1f, 1.f) * hside;
+            window->DrawList->AddRectFilled(ImVec2(lt.x + hside + (rpos.x * offset_block) - side, lt.y + hside + (rpos.y * offset_block) - side),
+                                            ImVec2(lt.x + hside + (rpos.x * offset_block) + side, lt.y + hside + (rpos.y * offset_block) + side),
+                                            color_alpha(c, 1.f));
+            ti++;
+        }
+    }
+
+    inline void SpinnerSquishSquare(const char *label, float radius, const ImColor &color, float speed)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+        
+        float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float side = ImSin((float)-start) * radius;
+        bool type = (start > IM_PI) ? 1 : 0;
+        if (type) {
+            if (start > IM_PI && start < IM_PI + PI_DIV_2) {
+                window->DrawList->AddRectFilled(ImVec2(centre.x - side, centre.y - radius), ImVec2(centre.x + side, centre.y + radius), color_alpha(color, 1.f));
+            } else {
+                window->DrawList->AddRectFilled(ImVec2(centre.x - radius, centre.y - side), ImVec2(centre.x + radius, centre.y + side), color_alpha(color, 1.f));
+            }
+        } else {
+            if (start < PI_DIV_2) {
+                window->DrawList->AddRectFilled(ImVec2(centre.x - radius, centre.y - side), ImVec2(centre.x + radius, centre.y + side), color_alpha(color, 1.f));
+            } else {
+                window->DrawList->AddRectFilled(ImVec2(centre.x - side, centre.y - radius), ImVec2(centre.x + side, centre.y + radius), color_alpha(color, 1.f));
+            }
+        }
+    }
+
+    inline void SpinnerFluid(const char *label, float radius, const ImColor &color, float speed, int bars = 3)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      const ImGuiStyle &style = GImGui->Style;
+      const float hspeed = 0.1f + ImSin((float)ImGui::GetTime() * 0.1f) * 0.05f;
+      constexpr float rkoeff[6][3] = {{0.15f, 0.1f, 0.1f}, {0.033f, 0.15f, 0.8f}, {0.017f, 0.25f, 0.6f}, {0.037f, 0.1f, 0.4f}, {0.25f, 0.1f, 0.3f}, {0.11f, 0.1f, 0.2f}};
+      const float j_k = radius * 2.f / num_segments;
+      float out_h, out_s, out_v;
+      ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+      for (int i = 0; i < bars; i++)
+      {
+        ImColor c = color_alpha(ImColor::HSV(out_h - i * 0.1f, out_s, out_v), rkoeff[i % 6][1]);
+        for (int j = 0; j < num_segments; ++j) {
+          float h = (0.6f + 0.3f * ImSin((float)ImGui::GetTime() * (speed * rkoeff[i % 6][2] * 2.f) + (2.f * rkoeff[i % 6][0] * j * j_k))) * (radius * 2.f * rkoeff[i % 6][2]);
+          window->DrawList->AddRectFilled(ImVec2(pos.x + style.FramePadding.x + j * j_k, centre.y + size.y / 2.f),
+                                          ImVec2(pos.x + style.FramePadding.x + (j + 1) * (j_k), centre.y + size.y / 2.f - h),
+                                          c);
+        }
+      }
+    }
+
+    inline void SpinnerFluidPoints(const char *label, float radius, float thickness, const ImColor &color, float speed, size_t dots = 6, float delta = 0.35f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const ImGuiStyle &style = GImGui->Style;
+        const float rkoeff[3] = {0.033f, 0.3f, 0.8f};
+        const float hspeed = 0.1f + ImSin((float)ImGui::GetTime() * 0.1f) * 0.05f;
+        const float j_k = radius * 2.f / num_segments;
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (int j = 0; j < num_segments; ++j) {
+            float h = (0.6f + delta * ImSin((float)ImGui::GetTime() * (speed * rkoeff[2] * 2.f) + (2.f * rkoeff[0] * j * j_k))) * (radius * 2.f * rkoeff[2]);
+            for (int i = 0; i < dots; i++) {
+                ImColor c = color_alpha(ImColor::HSV(out_h - i * 0.1f, out_s, out_v), 1.f);
+                window->DrawList->AddCircleFilled(ImVec2(pos.x + style.FramePadding.x + j * j_k, centre.y + size.y / 2.f - (h / dots) * i), thickness, c);
+            }
+        }
+    }
+
+    inline void SpinnerArcPolarFade(const char *label, float radius, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float arc_angle = PI_2 / (float)arcs;
+      const float angle_offset = arc_angle / num_segments;
+      constexpr float rkoeff[6][3] = {{0.15f, 0.1f, 0.1f}, {0.033f, 0.15f, 0.8f}, {0.017f, 0.25f, 0.6f}, {0.037f, 0.1f, 0.4f}, {0.25f, 0.1f, 0.3f}, {0.11f, 0.1f, 0.2f}};
+      for (size_t arc_num = 0; arc_num < arcs; ++arc_num)
+      {
+        const float b = arc_angle * arc_num - PI_DIV_2 - PI_DIV_4;
+        const float e = arc_angle * arc_num + arc_angle - PI_DIV_2 - PI_DIV_4;
+        const float a = arc_angle * arc_num;
+        float h = (0.6f + 0.3f * ImSin((float)ImGui::GetTime() * (speed * rkoeff[arc_num % 6][2] * 2.f) + (2 * rkoeff[arc_num % 6][0])));
+        ImColor c = color_alpha(color, h);
+
+        window->DrawList->PathClear();
+        window->DrawList->PathLineTo(centre);
+        for (size_t i = 0; i <= num_segments + 1; i++)
+        {
+          const float ar = arc_angle * arc_num + (i * angle_offset) - PI_DIV_2 - PI_DIV_4;
+          window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ar) * radius, centre.y + ImSin(ar) * radius));
+        }
+        window->DrawList->PathFillConvex(c);
+      }
+    }
+
+    inline void SpinnerArcPolarRadius(const char *label, float radius, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float arc_angle = PI_2 / (float)arcs;
+      const float angle_offset = arc_angle / num_segments;
+      constexpr float rkoeff[6][3] = {{0.15f, 0.1f, 0.41f}, {0.033f, 0.15f, 0.8f}, {0.017f, 0.25f, 0.6f}, {0.037f, 0.1f, 0.4f}, {0.25f, 0.1f, 0.3f}, {0.11f, 0.1f, 0.2f}};
+      float out_h, out_s, out_v;
+      ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+      for (size_t arc_num = 0; arc_num < arcs; ++arc_num)
+      {
+        const float b = arc_angle * arc_num - PI_DIV_2 - PI_DIV_4;
+        const float e = arc_angle * arc_num + arc_angle - PI_DIV_2 - PI_DIV_4;
+        const float a = arc_angle * arc_num;
+        float r = (0.6f + 0.3f * ImSin((float)ImGui::GetTime() * (speed * rkoeff[arc_num % 6][2] * 2.f) + (2.f * rkoeff[arc_num % 6][0])));
+
+        window->DrawList->PathClear();
+        window->DrawList->PathLineTo(centre);
+        ImColor c = ImColor::HSV(out_h + arc_num * 0.31f, out_s, out_v);
+        for (size_t i = 0; i <= num_segments + 1; i++)
+        {
+          const float ar = arc_angle * arc_num + (i * angle_offset) - PI_DIV_2 - PI_DIV_4;
+          window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ar) * (radius * r), centre.y + ImSin(ar) * (radius * r)));
+        }
+        window->DrawList->PathFillConvex(color_alpha(c, 1.f));
+      }
+    }
+
+    inline void SpinnerCaleidoscope(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t arcs = 6, int mode = 0)
+    {
+      SPINNER_HEADER(pos, size, centre, num_segments);
+
+      float start = (float)ImGui::GetTime() * speed;
+      float astart = ImFmod(start, PI_2 / arcs);
+      start -= astart;
+      const float angle_offset = PI_2 / arcs;
+      const float angle_offset_t = angle_offset * 0.3f;
+      arcs = ImMin<size_t>(arcs, 32);
+
+      auto get_points = [&] (float left, float right, float r) -> std::array<ImVec2, 4> {
+        const float rmin = r - thickness;
+        return {
+          ImVec2(centre.x + ImCos(left) * rmin, centre.y + ImSin(left) * rmin),
+          ImVec2(centre.x + ImCos(left) * r, centre.y + ImSin(left) * r),
+          ImVec2(centre.x + ImCos(right) * r, centre.y + ImSin(right) * r),
+          ImVec2(centre.x + ImCos(right) * rmin, centre.y + ImSin(right) * rmin)
+        };
+      };
+
+      auto draw_sectors = [&] (float s, const std::function<ImColor (size_t)>& color_func, float r) {
+        for (size_t i = 0; i <= arcs; i++) {
+          float left = s + (i * angle_offset) - angle_offset_t;
+          float right = s + (i * angle_offset) + angle_offset_t;
+          auto points = get_points(left, right, r);
+          window->DrawList->AddConvexPolyFilled(points.data(), 4, color_alpha(color_func(i), 1.f));
+        }
+      };
+
+      float out_h, out_s, out_v;
+      ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+      draw_sectors(start, [&] (size_t i) { return ImColor::HSV(out_h + i * 0.31f, out_s, out_v); }, radius);
+      switch (mode) {
+      case 0: draw_sectors(-start * 0.78f, [&] (size_t i) { return ImColor::HSV(out_h + i * 0.31f, out_s, out_v); }, radius - thickness - 2); break;
+      case 1:
+        {
+          ImColor c = color;
+          float lerp_koeff = (ImSin((float)ImGui::GetTime() * speed) + 1.f) * 0.5f;
+          c.Value.w = ImMax(0.1f, ImMin(lerp_koeff, 1.f));
+          float dr = radius - thickness - 3;
+          window->DrawList->AddCircleFilled(centre, dr, c, num_segments);
+        } 
+        break;
+      }
+    }
+
+    // spinner idea by nitz 'Chris Dailey'
+    inline void SpinnerHboDots(const char *label, float radius, float thickness, const ImColor &color = white, float minfade = 0.0f, float ryk = 0.f, float speed = 1.1f, size_t dots = 6)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+
+        for (size_t i = 0; i < dots; i++)
+        {
+            const float astart = start + PI_2_DIV(dots) * i;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(astart) * radius, centre.y + ryk * ImCos(astart) * radius), thickness,
+                                              color_alpha(color, ImMax(minfade, ImSin(astart + PI_DIV_2))),
+                                              8);
+        }
+    }
+
+    inline void SpinnerMoonDots(const char *label, float radius, float thickness, const ImColor &first, const ImColor &second, float speed = 1.1f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+
+        const float astart = ImFmod(start, IM_PI * 2.f);
+        const float bstart = astart + IM_PI;
+
+        const float sina = ImSin(astart);
+        const float sinb = ImSin(bstart);
+
+        if (astart < PI_DIV_2 || astart > IM_PI + PI_DIV_2) {
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + sina * thickness, centre.y), thickness, color_alpha(first, 1.f), 16);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + sinb * thickness, centre.y), thickness, color_alpha(second, 1.f), 16);
+            window->DrawList->AddCircle(ImVec2(centre.x + sinb * thickness, centre.y), thickness, color_alpha(first, 1.f), 16);
+        } else {
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + sinb * thickness, centre.y), thickness, color_alpha(second, 1.f), 16);
+            window->DrawList->AddCircle(ImVec2(centre.x + sinb * thickness, centre.y), thickness, color_alpha(first, 1.f), 16);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + sina * thickness, centre.y), thickness, color_alpha(first, 1.f), 16);
+        }
+    }
+
+    inline void SpinnerTwinHboDots(const char *label, float radius, float thickness, const ImColor &color = white, float minfade = 0.0f, float ryk = 0.f, float speed = 1.1f, size_t dots = 6, float delta = 0.f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+
+        for (size_t i = 0; i < dots; i++)
+        {
+            const float astart = start + PI_2_DIV(dots) * i;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(astart) * radius, centre.y + ryk * ImCos(astart) * radius + radius * delta), thickness,
+                                              color_alpha(color, ImMax(minfade, ImSin(astart + PI_DIV_2))),
+                                              8);
+        }
+
+        for (size_t i = 0; i < dots; i++)
+        {
+            const float astart = start + PI_2_DIV(dots) * i;
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(astart) * radius, centre.y - ryk * ImCos(astart) * radius - radius * delta), thickness,
+                                              color_alpha(color, ImMax(minfade, ImSin(astart + PI_DIV_2))),
+                                              8);
+        }
+    }
+
+    inline void SpinnerThreeDotsStar(const char *label, float radius, float thickness, const ImColor &color = white, float minfade = 0.0f, float ryk = 0.f, float speed = 1.1f, float delta = 0.f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(-start) * radius, centre.y - ryk * ImCos(-start) * radius + radius * delta), thickness, color_alpha(color, ImMax(minfade, ImSin(-start + PI_DIV_2))), 8);
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(start) * radius, centre.y - ryk * ImCos(start) * radius - radius * delta), thickness, color_alpha(color, ImMax(minfade, ImSin(start + PI_DIV_2))), 8);
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImSin(start + PI_DIV_4) * radius, centre.y - ryk * ImCos(start + PI_DIV_4) * radius - radius * delta), thickness, color_alpha(color, ImMax(minfade, ImSin(start + PI_DIV_4 + PI_DIV_2))), 8);
+    }
+
+    inline void SpinnerSineArcs(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        float length = ImFmod(start, IM_PI);
+        const float dangle = ImSin(length) * IM_PI * 0.35f;
+        const float angle_offset = IM_PI / num_segments;
+
+        auto draw_spring = [&] (float k) {
+            float arc = 0.f;
+            window->DrawList->PathClear();
+            for (size_t i = 0; i < num_segments; i++) {
+                float a = start + (i * angle_offset);
+                if (ImSin(a) < 0.f)
+                    a *= -1;
+                window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * radius, centre.y + k * ImSin(a) * radius));
+                arc += angle_offset;
+                if (arc > dangle)
+                    break;
+            }
+            window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+        };
+
+        draw_spring(1);
+        draw_spring(-1);
+    }
+
+    inline void SpinnerTrianglesShift(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bg = half_white, float speed = 2.8f, size_t bars = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        ImColor c = color;
+        float lerp_koeff = (ImSin((float)ImGui::GetTime() * speed) + 1.f) * 0.5f;
+        c.Value.w = ImMax(0.1f, ImMin(lerp_koeff, 1.f));
+
+        const float angle_offset = PI_2 / bars;
+        float start = (float)ImGui::GetTime() * speed;
+        const float astart = ImFmod(start, angle_offset);
+        const float save_start = start;
+        start -= astart;
+        const float angle_offset_t = angle_offset * 0.3f;
+        bars = ImMin<size_t>(bars, 32);
+
+        const float rmin = radius - thickness;
+        auto get_points = [&] (float left, float right, float r1, float r2) -> std::array<ImVec2, 4> {
+            return {
+                ImVec2(centre.x + ImCos(left) * r1, centre.y + ImSin(left) * r1),
+                ImVec2(centre.x + ImCos(left) * r2, centre.y + ImSin(left) * r2),
+                ImVec2(centre.x + ImCos(right) * r2, centre.y + ImSin(right) * r2),
+                ImVec2(centre.x + ImCos(right) * r1, centre.y + ImSin(right) * r1)
+            };
+        };
+
+        ImColor rc = bg;
+        for (size_t i = 0; i < bars; i++) {
+            float left = start + (i * angle_offset) - angle_offset_t;
+            float right = start + (i * angle_offset) + angle_offset_t;
+            float centera = start - PI_DIV_2 + (i * angle_offset);
+            float rmul = 1.f - ImClamp(ImAbs(centera - save_start), 0.f, PI_DIV_2) / PI_DIV_2;
+            rc.Value.w = ImMax(rmul, 0.1f);
+            rmul *= 1.5f;
+            rmul = ImMax(0.5f, rmul);
+            const float r1 = ImMax(rmin * rmul, rmin);
+            const float r2 = ImMax(radius * rmul, radius);
+            auto points = get_points(left, right, r1, r2);
+            window->DrawList->AddConvexPolyFilled(points.data(), 4, color_alpha(rc, 1.f));
+        }
+    }
+
+    inline void SpinnerPointsShift(const char *label, float radius, float thickness, const ImColor &color = white, const ImColor &bg = half_white, float speed = 2.8f, size_t bars = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        ImColor c = color;
+        float lerp_koeff = (ImSin((float)ImGui::GetTime() * speed) + 1.f) * 0.5f;
+        c.Value.w = ImMax(0.1f, ImMin(lerp_koeff, 1.f));
+
+        const float angle_offset = PI_2 / bars;
+        float start = (float)ImGui::GetTime() * speed;
+        const float astart = ImFmod(start, angle_offset);
+        const float save_start = start;
+        start -= astart;
+        const float angle_offset_t = angle_offset * 0.3f;
+        bars = ImMin<size_t>(bars, 32);
+
+        const float rmin = radius - thickness;
+
+        ImColor rc = bg;
+        for (size_t i = 0; i < bars; i++) {
+            float left = start + (i * angle_offset) - angle_offset_t;
+            float centera = start - PI_DIV_2 + (i * angle_offset);
+            float rmul = 1.f - ImClamp(ImAbs(centera - save_start), 0.f, PI_DIV_2) / PI_DIV_2;
+            rc.Value.w = ImMax(rmul, 0.1f);
+            rmul *= 1.f + ImSin(rmul * IM_PI);
+            const float r = ImMax(radius * rmul, radius);
+            window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(left) * r, centre.y + ImSin(left) * r), thickness, color_alpha(rc, 1.f), num_segments);
+        }
+    }
+
+    inline void SpinnerSwingDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = (float)ImGui::GetTime() * speed;
+        constexpr int elipses = 2;
+
+        auto get_rotated_ellipse_pos = [&] (float alpha, float start) {
+            std::array<ImVec2, 36> pts;
+
+            alpha = ImFmod(alpha, IM_PI);
+            float a = radius;
+            float b = radius / 10.f; 
+
+            float anga = ImFmod(start, PI_2);
+            float x = a * ImCos(anga) * ImCos(alpha) + b * ImSin(anga) * ImSin(alpha) + centre.x;
+            float y = b * ImSin(anga) * ImCos(alpha) - a * ImCos(anga) * ImSin(alpha) + centre.y;
+            return ImVec2{x, y};
+        };
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (int i = 0; i < elipses; ++i) {
+            ImVec2 ppos = get_rotated_ellipse_pos((IM_PI * (float)i/ elipses) + PI_DIV_4, start + PI_DIV_2 * i);
+            const float y_delta = ImAbs(centre.y - ppos.y);
+            float th_koeff = ImMax((y_delta / size.y) * 4.f, 0.5f);
+            window->DrawList->AddCircleFilled(ppos, th_koeff * thickness, color_alpha(ImColor::HSV(out_h + i * 0.5f, out_s, out_v), 1.f), num_segments);
+        }
+    }
+
+    inline void SpinnerCircularPoints(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 1.8f, int lines = 8)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, radius);
+        const float bg_angle_offset = (PI_2) / lines;
+        for (size_t j = 0; j < 3; ++j)
+        {
+            const float start_offset = j * radius / 3.f;
+            const float rmax = ImFmod((start + start_offset), radius);
+
+            ImColor c = color_alpha(color, ImSin((radius - rmax) / radius * IM_PI));
+            for (size_t i = 0; i < lines; i++)
+            {
+                float a = (i * bg_angle_offset);
+                window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(a) * rmax, centre.y + ImSin(a) * rmax), thickness, c, num_segments);
+            }
+        }
+    }
+
+    inline void SpinnerCurvedCircle(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t circles = 1)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+        const float bg_angle_offset = PI_2 / num_segments;
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        for (int j = 0; j < circles; j++)
+        {
+            window->DrawList->PathClear();
+            const float rr = radius - ((radius * 0.5f) / circles) * j;
+            const float start_a = start * (1.1f * (j+1));
+            for (size_t i = 0; i <= num_segments; i++)
+            {
+                const float a = start_a + (i * bg_angle_offset);
+                const float r = rr - (0.2f * (i % 2)) * rr;
+                window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a) * r, centre.y + ImSin(a) * r));
+            }
+            window->DrawList->PathStroke(color_alpha(ImColor::HSV(out_h + (j * 1.f / circles), out_s, out_v), 1.f), false, thickness);
+        }
+    }
+
+    inline void SpinnerModCircle(const char *label, float radius, float thickness, const ImColor &color = white, float ang_min = 1.f, float ang_max = 1.f, float speed = 2.8f)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+
+        window->DrawList->PathClear();
+        for (size_t i = 0; i <= 90; i++)
+        {
+            const float ax = ((i / 90.f) * PI_2 * ang_min);
+            const float ay = ((i / 90.f) * PI_2 * ang_max);
+            window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(ax) * radius, centre.y + ImSin(ay) * radius));
+        }
+        window->DrawList->PathStroke(color_alpha(color, 1.f), false, thickness);
+
+        start = (start < IM_PI) ? (start * 2.f) : (PI_2 - start) * 2.f;
+        window->DrawList->AddCircleFilled(ImVec2(centre.x + ImCos(start * ang_min) * radius, centre.y + ImSin(start * ang_max) * radius), thickness * 4.f, color_alpha(color, 1.f), num_segments);
+    }
+
+    inline void SpinnerDnaDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, int lt = 8, float delta = 0.5f, bool mode = 0)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 2.5f;
+        const float dots = (size.x / (thickness * nextItemKoeff));
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+
+        float out_h, out_s, out_v;
+        ImGui::ColorConvertRGBtoHSV(color.Value.x, color.Value.y, color.Value.z, out_h, out_s, out_v);
+        auto draw_point = [&] (float angle, int i) {
+            float a = angle + start + (IM_PI - i * PI_DIV(dots));
+            float th_koeff = 1.f + ImSin(a + PI_DIV_2) * 0.5f;
+
+            float pp = mode ? centre.x + ImSin(a) * size.x * delta
+                            : centre.y + ImSin(a) * size.y * delta;
+            ImColor c = ImColor::HSV(out_h + i * (1.f / dots * 2.f), out_s, out_v);
+            ImVec2 p = mode ? ImVec2(pp, centre.y - (size.y * 0.5f) + i * thickness * nextItemKoeff)
+                            : ImVec2(centre.x - (size.x * 0.5f) + i * thickness * nextItemKoeff, pp);
+            window->DrawList->AddCircleFilled(p, thickness * th_koeff, color_alpha(c, 1.f), lt);
+            return p;
+        };
+
+
+        for (int i = 0; i < dots; i++) {
+            ImVec2 p1 = draw_point(0, i);
+            ImVec2 p2 = draw_point(IM_PI, i);
+            window->DrawList->AddLine(p1, p2, color_alpha(color, 1.f), thickness * 0.5f);
+        }
+    }
+
+    inline void Spinner3SmuggleDots(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 4.8f, int lt = 8, float delta = 0.5f, bool mode = 0)     {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float nextItemKoeff = 2.5f;
+        const float dots = 2;// (size.x / (thickness * nextItemKoeff));
+        const float start = ImFmod((float)ImGui::GetTime() * speed, PI_2);
+
+        auto draw_point = [&] (float angle, int i, float k) {
+            float a = angle + k * start + k * (IM_PI - i * PI_DIV(dots));
+            float th_koeff = 1.f + ImSin(a + PI_DIV_2) * 0.3f;
+
+            float pp = mode ? centre.x + ImSin(a) * size.x * delta
+                            : centre.y + ImSin(a) * size.y * delta;
+            ImVec2 p = mode ? ImVec2(pp, centre.y - (size.y * 0.5f) + i * thickness * nextItemKoeff)
+                            : ImVec2(centre.x - (size.x * 0.5f) + i * thickness * nextItemKoeff, pp);
+            window->DrawList->AddCircleFilled(p, thickness * th_koeff, color_alpha(color, 1.f), lt);
+            return p;
+        };
+
+        {
+            ImVec2 p1 = draw_point(0, 1, -1);
+            ImVec2 p2 = draw_point(IM_PI, 2, 1);
+            //window->DrawList->AddLine(p1, p2, color_alpha(color, 1.f), thickness * 0.5f);
+            ImVec2 p3 = draw_point(PI_DIV_2, 3, -1);
+            //window->DrawList->AddLine(p2, p3, color_alpha(color, 1.f), thickness * 0.5f);
+        }
+    }
+
+    inline void SpinnerRotateSegmentsPulsar(const char *label, float radius, float thickness, const ImColor &color = white, float speed = 2.8f, size_t arcs = 4, size_t layers = 1)
+    {
+        SPINNER_HEADER(pos, size, centre, num_segments);
+
+        const float arc_angle = PI_2 / (float)arcs;
+        const float angle_offset = arc_angle / num_segments;
+        float r = radius;
+        float reverse = 1.f;
+
+        const float bg_angle_offset = PI_2_DIV(num_segments);
+        const float koeff = PI_DIV(2 * layers);
+        float start = (float)ImGui::GetTime() * speed;
+
+        for (int num_ring = 0; num_ring < layers; ++num_ring) {
+            float radius_k = ImSin(ImFmod(start + (num_ring * koeff), PI_DIV_2));
+            ImColor c = color_alpha(color, (radius_k > 0.5f) ? (2.f - (radius_k * 2.f)) : color.Value.w);
+
+            for (size_t arc_num = 0; arc_num < arcs; ++arc_num)
+            {
+                window->DrawList->PathClear();
+                for (size_t i = 2; i <= num_segments - 2; i++)
+                {
+                    const float a = start * (1.f + 0.1f * num_ring) + arc_angle * arc_num + (i * angle_offset);
+                    window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a * reverse) * (r * radius_k), centre.y + ImSin(a * reverse) * (r * radius_k)));
+                }
+                window->DrawList->PathStroke(c, false, thickness);
+            }
+        }
+    }
+
+    namespace detail {
+      static struct SpinnerDraw { SpinnerTypeT type; void (*func)(const char *, const detail::SpinnerConfig &); } spinner_draw_funcs[e_st_count] = {
+        { e_st_rainbow, [] (const char *label, const detail::SpinnerConfig &c) { SpinnerRainbow(label, c.m_Radius, c.m_Thickness, c.m_Color, c.m_Speed, c.m_AngleMin, c.m_AngleMax); } },
+        { e_st_angle,   [] (const char *label, const detail::SpinnerConfig &c) { SpinnerAng(label, c.m_Radius, c.m_Thickness, c.m_Color, c.m_BgColor, c.m_Speed, c.m_Angle); } },
+        { e_st_dots,    [] (const char *label, const detail::SpinnerConfig &c) { SpinnerDots(label, c.m_FloatPtr, c.m_Radius, c.m_Thickness, c.m_Color, c.m_Speed, c.m_Dots, c.m_MinThickness); } },
+        { e_st_ang,     [] (const char *label, const detail::SpinnerConfig &c) { SpinnerAng(label, c.m_Radius, c.m_Thickness, c.m_Color, c.m_BgColor, c.m_Speed, c.m_Angle); } },
+        { e_st_vdots,   [] (const char *label, const detail::SpinnerConfig &c) { SpinnerVDots(label, c.m_Radius, c.m_Thickness, c.m_Color, c.m_BgColor, c.m_Speed, c.m_Dots); } },
+        { e_st_bounce_ball, [] (const char *label,const detail::SpinnerConfig &c) { SpinnerBounceBall(label, c.m_Radius, c.m_Thickness, c.m_Color, c.m_Speed, c.m_Dots); } },
+        { e_st_eclipse, [] (const char *label, const detail::SpinnerConfig &c) { SpinnerAngEclipse(label , c.m_Radius, c.m_Thickness, c.m_Color, c.m_Speed); } },
+        { e_st_ingyang, [] (const char *label, const detail::SpinnerConfig &c) { SpinnerIngYang(label, c.m_Radius, c.m_Thickness, c.m_Reverse, c.m_Delta, c.m_AltColor, c.m_Color, c.m_Speed, c.m_Angle); } }
+      };
+    }
+
+    inline void Spinner(const char *label, const detail::SpinnerConfig& config)
+    {
+      if (config.m_SpinnerType < sizeof(detail::spinner_draw_funcs))
+        detail::spinner_draw_funcs[config.m_SpinnerType].func(label, config);
+    }
+
+    template<SpinnerTypeT Type, typename... Args>
+    inline void Spinner(const char *label, const Args&... args)
+    {
+      detail::SpinnerConfig config(SpinnerType{Type}, args...);
+      Spinner(label, config);
+    }
+
+#ifdef IMSPINNER_DEMO
+    inline void demoSpinners() {
+      static int hue = 0;
+      static float nextdot = 0, nextdot2;
+      static bool show_number = false;
+      
+      nextdot -= 0.07f;
+
+      static float velocity = 1.f;
+      static float widget_size = 50.f;
+
+      static int selected_idx = 0;
+      static ImColor spinner_filling_meb_bg;
+
+      constexpr int num_spinners = 200;
+
+      static int cci = 0, last_cci = 0;
+      static std::map<int, const char*> __nn; auto Name = [] (const char* v) { if (!__nn.count(cci)) { __nn[cci] = v; }; return __nn[cci]; };
+      static std::map<int, float> __rr; auto R = [] (float v) { if (!__rr.count(cci)) { __rr[cci] = v; }; return __rr[cci]; };
+      static std::map<int, float> __tt; auto T = [] (float v) { if (!__tt.count(cci)) { __tt[cci] = v; }; return __tt[cci];  };
+      static std::map<int, ImColor> __cc; auto C = [] (ImColor v) { if (!__cc.count(cci)) { __cc[cci] = v; }; return __cc[cci];  };
+      static std::map<int, ImColor> __cb; auto CB = [] (ImColor v) { if (!__cb.count(cci)) { __cb[cci] = v; }; return __cb[cci];  };
+      static std::map<int, bool> __hc; auto HC = [] (bool v) { if (!__hc.count(cci)) { __hc[cci] = v; }; return __hc[cci];  };
+      static std::map<int, bool> __hcb; auto HCB = [] (bool v) { if (!__hcb.count(cci)) { __hcb[cci] = v; }; return __hcb[cci];  };
+      static std::map<int, float> __ss; auto S = [] (float v) { if (!__ss.count(cci)) { __ss[cci] = v; }; return __ss[cci];  };
+      static std::map<int, float> __aa; auto A = [] (float v) { if (!__aa.count(cci)) { __aa[cci] = v; }; return __aa[cci];  };
+      static std::map<int, float> __amn; auto AMN = [] (float v) { if (!__amn.count(cci)) { __amn[cci] = v; }; return __amn[cci];  };
+      static std::map<int, float> __amx; auto AMX = [] (float v) { if (!__amx.count(cci)) { __amx[cci] = v; }; return __amx[cci];  };
+      static std::map<int, int> __dt; auto DT = [] (int v) { if (!__dt.count(cci)) { __dt[cci] = v; }; return __dt[cci];  };
+      static std::map<int, int> __mdt; auto MDT = [] (int v) { if (!__mdt.count(cci)) { __mdt[cci] = v; }; return __mdt[cci];  };
+      static std::map<int, float> __dd; auto D = [] (float v) { if (!__dd.count(cci)) { __dd[cci] = v; }; return __dd[cci];  };
+
+
+      const auto draw_spinner = [&](int spinner_idx, float widget_size)
+      {
+        const ImVec2 curpos_begin = ImGui::GetCursorPos();
+
+        ImGui::PushID(spinner_idx);
+        {
+          if (show_number) {
+              ImGui::Text("%04u", spinner_idx);
+          }
+
+          const bool is_selected = (selected_idx == spinner_idx);
+          if (ImGui::Selectable("", is_selected, 0, ImVec2(widget_size, widget_size))) {
+            selected_idx = spinner_idx;
+            last_cci = spinner_idx;
+          }
+
+          // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
+          if (is_selected) {
+              ImGui::SetItemDefaultFocus();
+          }
+
+          const float sp_radius = __rr.count(spinner_idx) ? __rr[spinner_idx] : 16.f;
+          const float sp_offset = (widget_size - sp_radius * 2.f ) / 2.f;
+          ImGui::SetCursorPos({curpos_begin.x + sp_offset, curpos_begin.y + sp_offset});
+
+#define $(i) i: cci = i;
+          switch (spinner_idx) {
+          case $( 0) ImSpinner::Spinner<e_st_rainbow>   (Name("Spinner"),
+                                                          Radius{R(16)}, Thickness{T(2)}, Color{ImColor::HSV(++hue * 0.005f, 0.8f, 0.8f)}, Speed{S(8) * velocity}, AngleMin{AMN(0.f)}, AngleMax{AMX(PI_2)}); break;
+          case $( 1) ImSpinner::Spinner<e_st_angle>     (Name("SpinnerAng"),
+                                                          Radius{R(16)}, Thickness{T(2)}, Color{C(white)}, BgColor{CB(ImColor(255, 255, 255, 128))}, Speed{S(8) * velocity}, Angle{A(IM_PI)}); break;
+          case $( 2) ImSpinner::Spinner<e_st_dots>      (Name("SpinnerDots"),
+                                                          Radius{R(16)}, Thickness{T(4)}, Color{C(white)}, FloatPtr{&nextdot}, Speed{S(1) * velocity}, Dots{DT(12)}, MinThickness{-1.f}); break;
+          case $( 3) ImSpinner::Spinner<e_st_ang>       (Name("SpinnerAngNoBg"),
+                                                          Radius{R(16)}, Thickness{T(2)}, Color{C(white)}, BgColor{CB(ImColor(255, 255, 255, 0))}, Speed{S(6) * velocity}, Angle{A(IM_PI)}); break;
+          case $( 4) ImSpinner::Spinner<e_st_ang>       (Name("SpinnerAng270"),
+                                                          Radius{R(16)}, Thickness{T(2)}, Color{C(white)}, BgColor{CB(ImColor(255, 255, 255, 128))}, Speed{S(6) * velocity}, Angle{A(0.75f * PI_2)}); break;
+          case $( 5) ImSpinner::Spinner<e_st_ang>       (Name("SpinnerAng270NoBg"),
+                                                          Radius{R(16)}, Thickness{T(2)}, Color{C(white)}, BgColor{CB(ImColor(255, 255, 255, 0))}, Speed{S(6) * velocity}, Angle{A(0.75f * PI_2)}); break;
+          case $( 6) ImSpinner::Spinner<e_st_vdots>     (Name("SpinnerVDots"),
+                                                          Radius{R(16)}, Thickness{T(4)}, Color{C(white)}, BgColor{CB(ImColor::HSV(hue * 0.0011f, 0.8f, 0.8f))}, Speed{S(2.7f) * velocity}, Dots{DT(12)}, MiddleDots{6}); break;
+          case $( 7) ImSpinner::Spinner<e_st_bounce_ball>(Name("SpinnerBounceBall"),
+                                                          Radius{R(16)}, Thickness{T(6)}, Color{C(white)}, Speed{S(4) * velocity}, Dots{DT(1)}); break;
+          case $( 8) ImSpinner::Spinner<e_st_eclipse>   (Name("SpinnerAngEclipse"),
+                                                          Radius{R(16)}, Thickness{T(5)}, Color{C(white)}, Speed{S(6) * velocity}); break;
+          case $( 9) ImSpinner::Spinner<e_st_ingyang>   (Name("SpinnerIngYang"),
+                                                          Radius{R(16)}, Thickness{T(5)}, Reverse{false}, Delta{D(0.f)}, Color{C(white)}, AltColor{ImColor(255, 0, 0)}, Speed{S(4) * velocity}, Angle{A(IM_PI * 0.8f)}); break;
+          case $(10) ImSpinner::SpinnerBarChartSine     (Name("SpinnerBarChartSine"),
+                                                          R(16), 4, C(white), S(6.8f) * velocity, 4, 0); break;
+          case $(11) ImSpinner::SpinnerBounceDots       (Name("SpinnerBounceDots"), R(16),
+                                                          T(6), C(white), S(6) * velocity, DT(3)); break;
+          case $(12) ImSpinner::SpinnerFadeDots         (Name("SpinnerFadeDots"), R(16),
+                                                          T(6), C(white), S(8) * velocity, DT(8)); break;
+          case $(13) ImSpinner::SpinnerScaleDots        (Name("SpinnerScaleDots"), R(16),
+                                                          T(6), C(white), S(7) * velocity, DT(8)); break;
+          case $(14) ImSpinner::SpinnerMovingDots       (Name("SpinnerMovingDots"), R(16),
+                                                          T(6), C(white), S(30) * velocity, DT(3)); break;
+          case $(15) ImSpinner::SpinnerRotateDots       (Name("SpinnerRotateDots"),
+                                                          R(16), T(6), C(white), S(4) * velocity, DT(2)); break;
+          case $(16) ImSpinner::SpinnerTwinAng          (Name("SpinnerTwinAng"),
+                                                          R(16), 16, T(6), C(white), CB(ImColor(255, 0, 0)), S(4) * velocity, A(IM_PI)); break;
+          case $(17) ImSpinner::SpinnerClock            (Name("SpinnerClock"),
+                                                          R(16), T(2), C(ImColor(255, 0, 0)), CB(white), S(4) * velocity); break;
+          case $(18) ImSpinner::SpinnerIngYang          (Name("SpinnerIngYangR"),
+                                                          R(16), T(5), true, 0.1f, C(white), CB(ImColor(255, 0, 0)), S(4) * velocity, A(IM_PI * 0.8f)); break;
+          case $(19) ImSpinner::SpinnerBarChartSine     (Name("SpinnerBarChartSine2"),
+                                                          R(16), T(4), ImColor::HSV(hue * 0.005f, 0.8f, 0.8f), S(4.8f) * velocity, 4, 1); break;
+          case $(20) ImSpinner::SpinnerTwinAng180       (Name("SpinnerTwinAng"),
+                                                          R(16), 12, T(4), C(white), CB(ImColor(255, 0, 0)), S(4) * velocity); break;
+          case $(21) ImSpinner::SpinnerTwinAng360       (Name("SpinnerTwinAng360"),
+                                                          R(16), 11, T(4), C(white), CB(ImColor(255, 0, 0)), S(4) * velocity); break;
+          case $(22) ImSpinner::SpinnerIncDots          (Name("SpinnerIncDots"),
+                                                          R(16), T(4), C(white), S(5.6f) * velocity, 6); break;
+          case $(23) nextdot2 -= 0.2f * velocity;
+                     ImSpinner::SpinnerDots             (Name("SpinnerDotsWoBg"),
+                                                          &nextdot2, R(16), T(4), C(white), S(0.3f) * velocity, 12, 0.f); break;
+          case $(24) ImSpinner::SpinnerIncScaleDots     (Name("SpinnerIncScaleDots"),
+                                                          R(16), T(4), C(white), S(6.6f) * velocity, 6); break;
+          case $(25) ImSpinner::SpinnerAng              (Name("SpinnerAng90"),
+                                                          R(16), T(6), C(white), CB(ImColor(255, 255, 255, 128)), S(8.f) * velocity, A(PI_DIV_2)); break;
+          case $(26) ImSpinner::SpinnerAng              (Name("SpinnerAng90"),
+                                                          R(16), 6, C(white), CB(ImColor(255, 255, 255, 0)), S(8.5f) * velocity, A(PI_DIV_2)); break;
+          case $(27) ImSpinner::SpinnerFadeBars         (Name("SpinnerFadeBars"),
+                                                          10, C(white), S(4.8f) * velocity, 3); break;
+          case $(28) ImSpinner::SpinnerPulsar           (Name("SpinnerPulsar"),
+                                                          R(16), T(2), C(white), S(1) * velocity); break;
+          case $(29) ImSpinner::SpinnerIngYang          (Name("SpinnerIngYangR2"),
+                                                          R(16), T(5), true, 3.f, C(white), CB(ImColor(255, 0, 0)), S(4) * velocity, A(IM_PI * 0.8f)); break;
+          case $(30) ImSpinner::SpinnerBarChartRainbow  (Name("SpinnerBarChartRainbow"),
+                                                          R(16), T(4), ImColor::HSV(hue * 0.005f, 0.8f, 0.8f), S(6.8f) * velocity, 4); break;
+          case $(31) ImSpinner::SpinnerBarsRotateFade   (Name("SpinnerBarsRotateFade"),
+                                                          8, 18, T(4), C(white), S(7.6f) * velocity, 6); break;
+          case $(32) ImSpinner::SpinnerFadeBars         (Name("SpinnerFadeScaleBars"),
+                                                          10, C(white), S(6.8f) * velocity, 3, true); break;
+          case $(33) ImSpinner::SpinnerBarsScaleMiddle  (Name("SpinnerBarsScaleMiddle"),
+                                                          6, C(white), S(8.8f) * velocity, 3); break;
+          case $(34) ImSpinner::SpinnerAngTwin          (Name("SpinnerAngTwin1"),
+                                                          R(16), 13, T(2), C(ImColor(255, 0, 0)), CB(white), S(6) * velocity, A(PI_DIV_2)); break;
+          case $(35) ImSpinner::SpinnerAngTwin          (Name("SpinnerAngTwin2"),
+                                                          13, 16, T(2), C(ImColor(255, 0, 0)), CB(white), S(6) * velocity, A(PI_DIV_2)); break;
+          case $(36) ImSpinner::SpinnerAngTwin          (Name("SpinnerAngTwin3"),
+                                                          13, 16, T(2), C(ImColor(255, 0, 0)), CB(white), S(6) * velocity, A(PI_DIV_2), 2); break;
+          case $(37) ImSpinner::SpinnerAngTwin          (Name("SpinnerAngTwin4"),
+                                                          R(16), 13, T(2), C(ImColor(255, 0, 0)), CB(white), S(6) * velocity, A(PI_DIV_2), 2); break;
+          case $(38) ImSpinner::SpinnerTwinPulsar       (Name("SpinnerTwinPulsar"),
+                                                          R(16), T(2), C(white), S(0.5f) * velocity, 2); break;
+          case $(39) ImSpinner::SpinnerAngTwin          (Name("SpinnerAngTwin4"),
+                                                          R(14), 13, T(3), C(ImColor(255, 0, 0)), CB(ImColor(0, 0, 0, 0)), S(5) * velocity, A(IM_PI / 1.5f), 2); break;
+          case $(40) ImSpinner::SpinnerBlocks           (Name("SpinnerBlocks"),
+                                                          R(16), T(7), C(ImColor(255, 255, 255, 30)), CB(ImColor::HSV(hue * 0.005f, 0.8f, 0.8f)), S(5) * velocity); break;
+          case $(41) ImSpinner::SpinnerTwinBall         (Name("SpinnerTwinBall"),
+                                                          R(16), 11, T(2), 2.5f, C(ImColor(255, 0, 0)), CB(white), S(6) * velocity, 2); break;
+          case $(42) ImSpinner::SpinnerTwinBall         (Name("SpinnerTwinBall2"),
+                                                          R(15), 19, T(2), 2.f, C(ImColor(255, 0, 0)), CB(white), S(6) * velocity, 3); break;
+          case $(43) ImSpinner::SpinnerTwinBall         (Name("SpinnerTwinBall2"),
+                                                          16, 16, T(2), 5.f, C(ImColor(255, 0, 0)), CB(white), S(5) * velocity, 1); break;
+          case $(44) ImSpinner::SpinnerAngTriple        (Name("SpinnerAngTriple"),
+                                                          16, 13, 10, T(1.3f), C(white), ImColor(255, 0, 0), white, S(5) * velocity, A(1.5f * IM_PI)); break;
+          case $(45) ImSpinner::SpinnerIncFullDots      (Name("SpinnerIncFullDots"),
+                                                          R(16), T(4), C(white), S(5.6f) * velocity, 4); break;
+          case $(46) ImSpinner::SpinnerGooeyBalls       (Name("SpinnerGooeyBalls"),
+                                                          R(16), C(white), S(2.f) * velocity); break;
+          case $(47) ImSpinner::SpinnerRotateGooeyBalls (Name("SpinnerRotateGooeyBalls2"),
+                                                          R(16), T(5), C(white), S(6.f) * velocity, 2); break;
+          case $(48) ImSpinner::SpinnerRotateGooeyBalls (Name("SpinnerRotateGooeyBalls3"),
+                                                          R(16), T(5), C(white), S(6.f) * velocity, 3); break;
+          case $(49) ImSpinner::SpinnerMoonLine         (Name("SpinnerMoonLine"),
+                                                          R(16), T(3), C(ImColor(200, 80, 0)), ImColor(80, 80, 80), S(5) * velocity); break;
+          case $(50) ImSpinner::SpinnerArcRotation      (Name("SpinnerArcRotation"),
+                                                          R(13), T(5), C(white), S(3) * velocity, DT(4)); break;
+          case $(51) ImSpinner::SpinnerFluid            (Name("SpinnerFluid"),
+                                                          R(16), C(ImColor(0, 0, 255)), S(3.8f) * velocity, 4); break;
+          case $(52) ImSpinner::SpinnerArcFade          (Name("SpinnerArcFade"),
+                                                          R(13), T(5), C(white), S(3) * velocity, 4); break;
+          case $(53) ImSpinner::SpinnerFilling          (Name("SpinnerFilling"),
+                                                          R(16), T(6), C(white), CB(ImColor(255, 0, 0)), S(4) * velocity); break;
+          case $(54) ImSpinner::SpinnerTopup            (Name("SpinnerTopup"),
+                                                          R(16), 12, C(ImColor(255, 0, 0)), ImColor(80, 80, 80), CB(white), S(1) * velocity);  break;
+          case $(55) ImSpinner::SpinnerFadePulsar       (Name("SpinnerFadePulsar"),
+                                                          R(16), C(white), S(1.5f) * velocity, 1);  break;
+          case $(56) ImSpinner::SpinnerFadePulsar       (Name("SpinnerFadePulsar2"),
+                                                          R(16), C(white), S(0.9f) * velocity, 2); break;
+          case $(57) ImSpinner::SpinnerPulsar           (Name("SpinnerPulsar"),
+                                                          R(16), T(2), C(white), S(1) * velocity, false); break;
+          case $(58) ImSpinner::SpinnerDoubleFadePulsar (Name("SpinnerDoubleFadePulsar"),
+                                                          R(16), T(2), C(white), S(2) * velocity); break;
+          case $(59) ImSpinner::SpinnerFilledArcFade    (Name("SpinnerFilledArcFade"),
+                                                          R(16), C(white), S(4) * velocity, 4); break;
+          case $(60) ImSpinner::SpinnerFilledArcFade    (Name("SpinnerFilledArcFade6"),
+                                                          R(16), C(white), S(6) * velocity, 6); break;
+          case $(61) ImSpinner::SpinnerFilledArcFade    (Name("SpinnerFilledArcFade6"),
+                                                          R(16), C(white), S(8) * velocity, 12); break;
+          case $(62) ImSpinner::SpinnerFilledArcColor   (Name("SpinnerFilledArcColor"),
+                                                          R(16), C(ImColor(255, 0, 0)), CB(white), S(2.8f) * velocity, 4); break;
+          case $(63) ImSpinner::SpinnerCircleDrop       (Name("SpinnerCircleDrop"),
+                                                          R(16), T(1.5f), 4.f, C(ImColor(255, 0, 0)), CB(white), S(2.8f) * velocity, A(IM_PI)); break;
+          case $(64) ImSpinner::SpinnerSurroundedIndicator(Name("SpinnerSurroundedIndicator"),
+                                                          R(16), T(5), C(ImColor(0, 0, 0)), CB(white), S(7.8f) * velocity); break;
+          case $(65) ImSpinner::SpinnerTrianglesSelector (Name("SpinnerTrianglesSelector"),
+                                                          R(16), T(8), C(ImColor(0, 0, 0)), CB(white), S(4.8f) * velocity, 8); break;
+          case $(66) ImSpinner::SpinnerFlowingGradient  (Name("SpinnerFlowingFradient"),
+                                                          R(16), T(6), C(ImColor(200, 80, 0)), CB(ImColor(80, 80, 80)), S(5) * velocity, A(PI_2)); break;
+          case $(67) ImSpinner::SpinnerRotateSegments   (Name("SpinnerRotateSegments"),
+                                                          R(16), T(4), C(white), S(3) * velocity, 4); break;
+          case $(68) ImSpinner::SpinnerRotateSegments   (Name("SpinnerRotateSegments2"),
+                                                          R(16), T(3), C(white), S(2.4f) * velocity, 4, 2); break;
+          case $(69) ImSpinner::SpinnerRotateSegments   (Name("SpinnerRotateSegments3"),
+                                                          R(16), T(2), C(white), S(2.1f) * velocity, 4, 3); break;
+          case $(70) ImSpinner::SpinnerLemniscate       (Name("SpinnerLemniscate"),
+                                                          R(20), T(3), C(white), S(2.1f) * velocity, 3); break;
+          case $(71) ImSpinner::SpinnerRotateGear       (Name("SpinnerRotateGear"),
+                                                          R(16), T(6), C(white), S(2.1f) * velocity, 8); break;
+          case $(72) ImSpinner::SpinnerRotatedAtom      (Name("SpinnerRotatedAtom"),
+                                                          R(16), T(2), C(white), S(2.1f) * velocity, 3); break;
+          case $(73) ImSpinner::SpinnerAtom             (Name("SpinnerAtom"),
+                                                          R(16), T(2), C(white), S(4.1f) * velocity, 3); break;
+          case $(74) ImSpinner::SpinnerRainbowBalls     (Name("SpinnerRainbowBalls"),
+                                                          R(16.0f), T(4.0f), ImColor::HSV(0.25f, 0.8f, 0.8f, 0.f), S(1.5f) * velocity, (int)D(5.0f)); break;
+          case $(75) ImSpinner::SpinnerCamera           (Name("SpinnerCamera"),
+                                                          R(16), T(8), [] (int i) { return ImColor::HSV(i * 0.25f, 0.8f, 0.8f); }, S(4.8f) * velocity, 8); break;
+          case $(76) ImSpinner::SpinnerArcPolarFade     (Name("SpinnerArcPolarFade"),
+                                                          R(16), C(white), S(6) * velocity, 6); break;
+          case $(77) ImSpinner::SpinnerArcPolarRadius   (Name("SpinnerArcPolarRadius"),
+                                                          R(16), C(ImColor::HSV(0.25f, 0.8f, 0.8f)), S(6.f) * velocity, 6); break;
+          case $(78) ImSpinner::SpinnerCaleidoscope     (Name("SpinnerArcPolarPies"),
+                                                          R(16), T(4), C(ImColor::HSV(0.25f, 0.8f, 0.8f)), S(2.6f) * velocity, 10, 0); break;
+          case $(79) ImSpinner::SpinnerCaleidoscope     (Name("SpinnerArcPolarPies2"),
+                                                          R(16), T(4), C(ImColor::HSV(0.35f, 0.8f, 0.8f)), S(3.2f) * velocity, 10, 1); break;
+          case $(80) ImSpinner::SpinnerScaleBlocks      (Name("SpinnerScaleBlocks"),
+                                                          R(16), T(8), ImColor::HSV(hue * 0.005f, 0.8f, 0.8f), S(5) * velocity); break;
+          case $(81) ImSpinner::SpinnerRotateTriangles  (Name("SpinnerRotateTriangles"),
+                                                          R(16), T(2), C(white), S(6.f) * velocity, 3); break;
+          case $(82) ImSpinner::SpinnerArcWedges        (Name("SpinnerArcWedges"),
+                                                          R(16), C(ImColor::HSV(0.3f, 0.8f, 0.8f)), S(2.8f) * velocity, 4); break;
+          case $(83) ImSpinner::SpinnerScaleSquares     (Name("SpinnerScaleSquares"),
+                                                          R(16), T(8), ImColor::HSV(hue * 0.005f, 0.8f, 0.8f), S(5) * velocity); break;
+          case $(84) ImSpinner::SpinnerHboDots          (Name("SpinnerHboDots"), R(16),
+                                                          T(4), C(white), 0.f, 0.f, S(1.1f) * velocity, DT(6)); break;
+          case $(85) ImSpinner::SpinnerHboDots          (Name("SpinnerHboDots2"), R(16),
+                                                          T(4), C(white), 0.1f, 0.5f, S(1.1f) * velocity, DT(6)); break;
+          case $(86) ImSpinner::Spinner<e_st_bounce_ball>(Name("SpinnerBounceBall3"),
+                                                          Radius{R(16)}, Thickness{T(4)}, Color{C(white)}, Speed{S(3.2f) * velocity}, Dots{DT(5)}); break;
+          case $(87) ImSpinner::SpinnerBounceBall       (Name("SpinnerBounceBallShadow"),
+                                                          R(16), T(4), C(white), S(2.2f) * velocity, DT(1), true); break;
+          case $(88) ImSpinner::SpinnerBounceBall       (Name("SpinnerBounceBall5Shadow"),
+                                                          R(16), T(4), C(white), S(3.6f) * velocity, DT(5), true); break;
+          case $(89) ImSpinner::SpinnerSquareStrokeFade (Name("SpinnerSquareStrokeFade"),
+                                                          R(13), T(5), C(white), S(3) * velocity); break;
+          case $(90) ImSpinner::SpinnerSquareStrokeFill (Name("SpinnerSquareStrokeFill"),
+                                                          R(13), T(5), C(white), S(3) * velocity); break;
+          case $(91) ImSpinner::SpinnerSwingDots        (Name("SpinnerSwingDots"),
+                                                          R(16), T(6), C(ImColor(255, 0, 0)), S(4.1f) * velocity); break;
+          case $(92) ImSpinner::SpinnerRotateWheel      (Name("SpinnerRotateWheel"),
+                                                          R(16), T(10), C(ImColor(255, 255, 0)), CB(white), S(2.1f) * velocity, 8); break;
+          case $(93) ImSpinner::SpinnerWaveDots         (Name("SpinnerWaveDots"), R(16),
+                                                          T(3), C(white), S(6) * velocity, DT(8)); break;
+          case $(94) ImSpinner::SpinnerRotateShapes     (Name("SpinnerRotateShapes"),
+                                                          R(16), T(2), C(white), S(6.f) * velocity, DT(4), MDT(4)); break;
+          case $(95) ImSpinner::SpinnerSquareStrokeLoading(Name("SpinnerSquareStrokeLoanding"),
+                                                          R(13), T(5), C(white), S(3) * velocity); break;
+          case $(96) ImSpinner::SpinnerSinSquares       (Name("SpinnerSinSquares"),
+                                                          R(16), T(2), C(white), S(1.f) * velocity); break;
+          case $(97) ImSpinner::SpinnerZipDots          (Name("SpinnerZipDots"), R(16),
+                                                          T(3), C(white), S(6) * velocity, DT(5)); break;
+          case $(98) ImSpinner::SpinnerDotsToBar        (Name("SpinnerDotsToBar"), R(16),
+                                                          T(3), D(0.5f), C(ImColor::HSV(0.31f, 0.8f, 0.8f)), S(5) * velocity, DT(5)); break;
+          case $(99) ImSpinner::SpinnerSineArcs         (Name("SpinnerSineArcs"), R(16),
+                                                          T(1), C(white), S(3) * velocity);
+          case $(100) ImSpinner::SpinnerTrianglesShift  (Name("SpinnerTrianglesShift"),
+                                                          R(16), T(8), C(ImColor(0, 0, 0)), CB(white), S(1.8f) * velocity, DT(8)); break;
+          case $(101) ImSpinner::SpinnerCircularLines   (Name("SpinnerCircularLines"),
+                                                          R(16), C(white), S(1.5f) * velocity, DT(8));  break;
+          case $(102) ImSpinner::SpinnerLoadingRing     (Name("SpinnerLoadingRing"),
+                                                          R(16), T(6), C(red), CB(ImColor(255, 255, 255, 128)), S(1.f) * velocity, DT(5)); break;
+          case $(103) ImSpinner::SpinnerPatternRings    (Name("SpinnerPatternRings"),
+                                                          R(16), T(2), C(white), S(4.1f) * velocity, DT(3)); break;
+          case $(104) ImSpinner::SpinnerPatternSphere   (Name("SpinnerPatternSphere"),
+                                                          R(16), T(2), C(white), S(2.1f) * velocity, DT(6)); break;
+          case $(105) ImSpinner::SpinnerRingSynchronous (Name("SpinnerRingSnchronous"),
+                                                          R(16), T(2), C(white), S(2.1f) * velocity, DT(3)); break;
+          case $(106) ImSpinner::SpinnerRingWatermarks  (Name("SpinnerRingWatermarks"),
+                                                          R(16), T(2), C(white), S(2.1f) * velocity, DT(3)); break;
+          case $(107) ImSpinner::SpinnerFilledArcRing   (Name("SpinnerFilledArcRing"),
+                                                          R(16), T(6), C(red), CB(white), S(2.8f) * velocity, DT(8)); break;
+          case $(108) ImSpinner::SpinnerPointsShift     (Name("SpinnerPointsShift"),
+                                                          R(16), T(3), C(ImColor(0, 0, 0)), CB(white), S(1.8f) * velocity, DT(10)); break;
+          case $(109) ImSpinner::SpinnerCircularPoints  (Name("SpinnerCircularPoints"),
+                                                          R(16), T(1.2f), C(white), S(10.f) * velocity, DT(7));  break;
+          case $(110) ImSpinner::SpinnerCurvedCircle    (Name("SpinnerCurvedCircle"),
+                                                          R(16), T(1.2f), C(white), S(1.f) * velocity, DT(3));  break;
+          case $(111) ImSpinner::SpinnerModCircle       (Name("SpinnerModCirclre"),
+                                                          R(16), T(1.2f), C(white), AMN(1.f), AMX(2.f), S(3.f) * velocity);  break;
+          case $(112) ImSpinner::SpinnerModCircle       (Name("SpinnerModCirclre2"),
+                                                          R(16), T(1.2f), C(white), AMN(1.11f), AMX(3.33f), S(3.f) * velocity);  break;
+          case $(113) ImSpinner::SpinnerPatternEclipse  (Name("SpinnerPatternEclipse"),
+                                                          R(16), T(2), C(white), S(4.1f) * velocity, DT(5), AMN(2.f), AMX(0.f)); break;
+          case $(114) ImSpinner::SpinnerPatternEclipse  (Name("SpinnerPatternEclipse2"),
+                                                          R(16), T(2), C(white), S(4.1f) * velocity, DT(9), AMN(4.f), AMX(1.f)); break;
+          case $(115) ImSpinner::SpinnerMultiFadeDots   (Name("SpinnerMultiFadeDots"), R(16),
+                                                          T(2), C(white), S(8) * velocity, DT(8)); break;
+          case $(116) ImSpinner::SpinnerRainbowShot     (Name("SpinnerRainbowShot"),
+                                                          R(16), T(4), ImColor::HSV(0.25f, 0.8f, 0.8f, 0.f), S(1.5f) * velocity, DT(5)); break;
+          case $(117) ImSpinner::SpinnerSpiral          (Name("SpinnerSpiral"),
+                                                          R(16), T(2), C(white), S(6) * velocity, DT(5)); break;
+          case $(118) ImSpinner::SpinnerSpiralEye       (Name("SpinnerSpiralEye"),
+                                                          R(16), T(1), C(white), S(3) * velocity); break;
+          case $(119) ImSpinner::SpinnerWifiIndicator   (Name("SpinnerWifiIndicator"),
+                                                          R(16), T(1.5f), C(ImColor(0, 0, 0)), CB(white), S(7.8f) * velocity, AMN(5.52f), DT(3)); break;
+          case $(120) ImSpinner::SpinnerHboDots         (Name("SpinnerHboDots"), R(16),
+                                                          T(2), C(white), 0.f, 0.f, S(1.1f) * velocity, DT(10)); break;
+          case $(121) ImSpinner::SpinnerHboDots         (Name("SpinnerHboDots2"), R(16),
+                                                          T(4), C(white), 0.1f, 0.5f, S(1.1f) * velocity, DT(2)); break;
+          case $(122) ImSpinner::SpinnerHboDots         (Name("SpinnerHboDots4"), R(16),
+                                                          T(4), C(white), 0.1f, 0.5f, S(1.1f) * velocity, DT(3)); break;
+          case $(123) ImSpinner::SpinnerDnaDots         (Name("SpinnerDnaDotsH"), R(16),
+                                                          T(3), C(white), S(2) * velocity, DT(8), D(0.25f)); break;
+          case $(124) ImSpinner::SpinnerDnaDots         (Name("SpinnerDnaDotsV"), R(16),
+                                                          T(3), C(white), S(2) * velocity, DT(8), D(0.25f), true); break;
+          case $(125) ImSpinner::SpinnerRotateDots      (Name("SpinnerRotateDots2"),
+                                                          R(16), T(6), C(white), S(4) * velocity, ImMax<int>(int(ImSin((float)ImGui::GetTime() * 0.5f) * 8), 3)); break;
+          case $(126) ImSpinner::SpinnerSevenSegments   (Name("SpinnerSevenSegments"), "012345679ABCDEF",
+                                                          R(16), T(2), C(white), S(4) * velocity); break;
+          case $(127) ImSpinner::SpinnerSolarBalls      (Name("SpinnerSolarBalls"),
+                                                          R(16), T(4), C(red), CB(white), S(5) * velocity, DT(4)); break;
+          case $(128) ImSpinner::SpinnerSolarArcs       (Name("SpinnerSolarArcs"),
+                                                          R(16), T(4), C(red), CB(white), S(5) * velocity, DT(4)); break;
+          case $(129) ImSpinner::SpinnerRainbow         (Name("Spinner"),
+                                                          R(16), T(2), ImColor::HSV(++hue * 0.005f, 0.8f, 0.8f), S(8) * velocity, AMN(0.f), AMX(PI_2), DT(3)); break;
+          case $(130) ImSpinner::SpinnerRotatingHeart   (Name("SpinnerRotatedHeart"),
+                                                          R(16), T(2), C(red), S(8) * velocity, AMN(0.f)); break;
+          case $(131) ImSpinner::SpinnerSolarScaleBalls (Name("SpinnerSolarScaleBalls"),
+                                                          R(16), T(1.3f), C(red), S(1) * velocity, DT(36)); break;
+          case $(132) ImSpinner::SpinnerOrionDots       (Name("SpinnerOrionDots"),
+                                                          R(16), T(1.3f), C(white), S(4) * velocity, DT(12)); break;
+          case $(133) ImSpinner::SpinnerGalaxyDots      (Name("SpinnerGalaxyDots"),
+                                                          R(16), T(1.3f), C(white), S(0.2f) * velocity, DT(6)); break;
+          case $(134) ImSpinner::SpinnerAsciiSymbolPoints(Name("SpinnerAsciiSymbolPoints"), "012345679ABCDEF",
+                                                          R(16), T(2), C(white), S(4) * velocity); break;
+          case $(135) ImSpinner::SpinnerRainbowCircle   (Name("SpinnerRainbowCircle"),
+                                                          R(16), T(4), C(ImColor::HSV(0.25f, 0.8f, 0.8f)), S(1) * velocity, DT(4)); break;
+          case $(136) ImSpinner::SpinnerRainbowCircle   (Name("SpinnerRainbowCircle2"),
+                                                          R(16), T(2), ImColor::HSV(hue * 0.001f, 0.8f, 0.8f), S(2) * velocity, DT(8), D(0)); break;
+          case $(137) ImSpinner::Spinner<e_st_vdots>    (Name("SpinnerVDots2"),
+                                                          Radius{R(16)}, Thickness{T(4)}, Color{C(white)}, BgColor{CB(ImColor::HSV(hue * 0.0011f, 0.8f, 0.8f))}, Speed{S(2.1f) * velocity}, Dots{DT(2)}, MiddleDots{6}); break;
+          case $(138) ImSpinner::Spinner<e_st_vdots>    (Name("SpinnerVDots3"),
+                                                          Radius{R(16)}, Thickness{T(4)}, Color{C(white)}, BgColor{CB(ImColor::HSV(hue * 0.0011f, 0.8f, 0.8f))}, Speed{S(2.9f) * velocity}, Dots{DT(3)}, MiddleDots{6}); break;
+          case $(139) ImSpinner::SpinnerSquareRandomDots(Name("SpinnerSquareRandomDots"),
+                                                          R(16), T(2.8f), C(ImColor(255, 255, 255, 30)), CB(ImColor::HSV(hue * 0.005f, 0.8f, 0.8f)), S(5) * velocity); break;
+          case $(140) ImSpinner::SpinnerFluidPoints     (Name("SpinnerFluidPoints"),
+                                                          R(16), T(2.8f), C(ImColor(0, 0, 255)), S(3.8f) * velocity, Dots{DT(4)}, D(0.45f)); break;
+          case $(141) ImSpinner::SpinnerDotsLoading     (Name("SpinnerDotsLoading"),
+                                                          R(16), T(4.f), C(white), CB(white), S(2.f) * velocity); break;
+          case $(142) ImSpinner::SpinnerDotsToPoints    (Name("SpinnerDotsToPoints"), R(16),
+                                                          T(3), D(0.5f), C(ImColor::HSV(0.31f, 0.8f, 0.8f)), S(1.8f) * velocity, DT(5)); break;
+          case $(143) ImSpinner::SpinnerThreeDots       (Name("SpinnerThreeDots"), R(16),
+                                                          T(6), C(white), S(4) * velocity, DT(8)); break;
+          case $(144) ImSpinner::Spinner4Caleidospcope  (Name("Spinner4Caleidospcope"), R(16),
+                                                          T(6), ImColor::HSV(hue * 0.0031f, 0.8f, 0.8f), S(4.0f) * velocity, DT(8)); break;
+          case $(145) ImSpinner::SpinnerFiveDots        (Name("SpinnerSixDots"), R(16),
+                                                          T(6), C(white), S(4) * velocity, DT(8)); break;
+          case $(146) ImSpinner::SpinnerFillingMem      (Name("SpinnerFillingMem"),
+                                                          R(16), T(6), ImColor::HSV(hue * 0.001f, 0.8f, 0.8f), spinner_filling_meb_bg, S(4) * velocity); break;
+          case $(147) ImSpinner::SpinnerHerbertBalls    (Name("SpinnerHerbertBalls"),
+                                                          R(16), T(2.3f), C(white), S(2.0f) * velocity, DT(4)); break;
+          case $(148) ImSpinner::SpinnerHerbertBalls3D  (Name("SpinnerHerbertBalls3D"),
+                                                          R(16), T(3.f), C(white), S(1.4f) * velocity); break;
+          case $(149) ImSpinner::SpinnerSquareLoading   (Name("SpinnerSquareLoanding"),
+                                                          R(16), T(2.0f), C(white), S(3.0f) * velocity); break;
+          case $(150) ImSpinner::SpinnerTextFading      (Name("SpinnerTextFading"), "Loading",
+                                                          R(16), T(15.0f), C(ImColor::HSV(hue * 0.0011f, 0.8f, 0.8f)), S(4) * velocity); break;
+          case $(151) ImSpinner::SpinnerBarChartAdvSine (Name("SpinnerBarChartAdvSine"),
+                                                          R(16), T(5.0f), C(white), S(4.8f) * velocity, 0); break;
+          case $(152) ImSpinner::SpinnerBarChartAdvSineFade(Name("SpinnerBarChartAdvSineFade"),
+                                                          R(16), T(5.0f), C(white), S(4.8f) * velocity, 0); break;
+          case $(153) ImSpinner::SpinnerMovingArcs       (Name("SpinnerMovingArcs"),
+                                                          R(16), T(4.0f), C(white), S(2.0f) * velocity, DT(4)); break;
+          case $(154) ImSpinner::SpinnerFadeTris         (Name("SpinnerFadeTris"),
+                                                          R(20), C(white), S(5.f) * velocity, DT(2)); break;
+          case $(155) ImSpinner::SpinnerBounceDots       (Name("SpinnerBounceDots"), R(16),
+                                                          T(2.5f), C(white), S(3.0f) * velocity, DT(6), 1); break;
+          case $(156) ImSpinner::SpinnerRotateDots       (Name("SpinnerRotateDots"),
+                                                          R(16), T(2), C(white), S(4) * velocity, DT(16), 1); break;
+          case $(157) ImSpinner::SpinnerTwinAng360       (Name("SpinnerTwinAng360"),
+                                                          R(16), 11, T(2), C(white), CB(ImColor(255, 0, 0)), 2.4f, 2.1f, 1); break;
+          case $(158) ImSpinner::SpinnerAngTwin          (Name("SpinnerAngTwin1"),
+                                                          R(18), 13, T(2), C(ImColor(255, 0, 0)), CB(white), S(3) * velocity, A(1.3f), DT(3), 1); break;
+          case $(159) ImSpinner::SpinnerGooeyBalls       (Name("SpinnerGooeyBalls"),
+                                                          R(16), C(white), S(2.f) * velocity, 1); break;
+          case $(160) ImSpinner::SpinnerArcRotation      (Name("SpinnerArcRotation"),
+                                                          R(13), T(2.5), C(white), S(3) * velocity, DT(15), 1); break;
+          case $(161) ImSpinner::SpinnerAng              (Name("SpinnerAng90Gravity"),
+                                                          R(16), T(1), C(white), CB(ImColor(255, 255, 255, 128)), S(8.f) * velocity, A(PI_DIV_2), 1); break;
+          case $(162) ImSpinner::SpinnerAng              (Name("SpinnerAng90SinRad"),
+                                                          R(16), T(1), C(white), CB(ImColor(255, 255, 255, 0)), S(8.f) * velocity, A(0.75f * PI_2), 2); break;
+          case $(163) ImSpinner::SpinnerSquishSquare     (Name("SpinnerSquishSquare"),
+                                                          R(16), C(white), S(8.f) * velocity); break;
+          case $(164) ImSpinner::SpinnerPulsarBall       (Name("SpinnerBounceBall"),
+                                                          R(16), T(2), C(white), S(4) * velocity, DT(1)); break;
+          case $(165) ImSpinner::SpinnerRainbowMix       (Name("Spinner"),
+                                                          R(16), T(2), ImColor::HSV(0.005f, 0.8f, 0.8f), S(8) * velocity, AMN(0.f), AMX(PI_2), DT(5), 1); break;
+          case $(166) ImSpinner::SpinnerAngMix           (Name("SpinnerAngMix"),
+                                                          R(16), T(1), C(white), S(8.f) * velocity, A(IM_PI), DT(4), 0); break;
+          case $(167) ImSpinner::SpinnerAngMix           (Name("SpinnerAngMixGravity"),
+                                                          R(16), T(1), C(white), S(8.f) * velocity, A(PI_DIV_2), DT(6), 1); break;
+          case $(168) ImSpinner::SpinnerScaleBlocks      (Name("SpinnerScaleBlocks"),
+                                                          R(16), T(8), ImColor::HSV(hue * 0.005f, 0.8f, 0.8f), S(5) * velocity, 1); break;
+          case $(169) ImSpinner::SpinnerFadeDots         (Name("SpinnerFadeDots3"), R(16),
+                                                          T(6), C(white), S(8) * velocity, DT(4), 1); break;
+          case $(170) ImSpinner::SpinnerFadeDots         (Name("SpinnerFadeDots6"), R(16),
+                                                          T(3), C(white), S(8) * velocity, DT(4), 1); break;
+          case $(171) ImSpinner::SpinnerFadeDots         (Name("SpinnerFadeDots2"), R(16),
+                                                          T(2), C(white), S(5) * velocity, DT(8)); break;
+          case $(172) ImSpinner::SpinnerScaleDots        (Name("SpinnerScaleDots2"), R(16),
+                                                          T(2), C(white), S(4) * velocity, DT(8)); break;
+          case $(173) ImSpinner::Spinner3SmuggleDots     (Name("Spinner3SmuggleDots"), R(16),
+                                                          T(3), C(white), S(4) * velocity, DT(8), D(0.25f), true); break;
+          case $(174) ImSpinner::SpinnerSimpleArcFade    (Name("SpinnerSimpleArcFade"),
+                                                          R(13), T(2), C(white), S(4) * velocity); break;
+          case $(175) ImSpinner::SpinnerTwinHboDots      (Name("SpinnerTwinHboDots"), R(16),
+                                                          T(4), C(white), 0.1f, 0.5f, S(1.1f) * velocity, DT(6), D(0.f)); break;
+          case $(176) ImSpinner::SpinnerTwinHboDots      (Name("SpinnerTwinHboDots2"), R(16),
+                                                          T(4), C(white), 0.1f, 0.5f, S(3.1f) * velocity, DT(3), D(-0.5f)); break;
+          case $(177) ImSpinner::SpinnerThreeDotsStar    (Name("SpinnerThreeDotsStar"), R(16),
+                                                          T(4), C(white), 0.1f, 0.5f, S(5.1f) * velocity, D(-0.2f)); break;
+          case $(178) ImSpinner::SpinnerSquareSpins      (Name("SpinnerSquareSpins"), R(16),
+                                                          T(6), C(white), S(2) * velocity); break;
+          case $(179) ImSpinner::SpinnerMoonDots         (Name("SpinnerMoonDots"), R(16),
+                                                          T(8), C(white), CB(ImColor(0, 0, 0)), S(1.1f) * velocity); break;
+          case $(180) ImSpinner::SpinnerFilledArcFade    (Name("SpinnerFilledArcFade7"),
+                                                          R(16), C(white), S(6) * velocity, DT(6), 1); break;
+          case $(181) ImSpinner::SpinnerRotateSegmentsPulsar(Name("SpinnerRotateSegmentsPulsar"),
+                                                          R(16), T(2), C(white), S(1.1f) * velocity, DT(4), MDT(2)); break;
+          case $(182) ImSpinner::SpinnerRotateSegmentsPulsar(Name("SpinnerRotateSegmentsPulsar2"),
+                                                          R(16), T(2), C(white), S(1.1f) * velocity, DT(1), MDT(3)); break;
+          case $(183) ImSpinner::SpinnerRotateSegmentsPulsar(Name("SpinnerRotateSegmentsPulsar3"),
+                                                          R(16), T(2), C(white), S(1.1f) * velocity, DT(3), MDT(3)); break;
+          case $(184) ImSpinner::SpinnerPointsArcBounce  (Name("SpinnerPointsArcBounce"),
+                                                          R(16), T(2), C(white), S(3) * velocity, DT(12), 1, 0.f); break;
+          case $(185) ImSpinner::SpinnerSomeScaleDots    (Name("SpinnerSomeScaleDots0"),
+                                                          R(16), T(4), C(white), S(5.6f) * velocity, 6, 0); break;
+          case $(186) ImSpinner::SpinnerSomeScaleDots    (Name("SpinnerSomeScaleDots1"),
+                                                          R(16), T(4), C(white), S(6.6f) * velocity, 6, 1); break;
+          case $(187) ImSpinner::SpinnerPointsArcBounce  (Name("SpinnerPointsArcBounce2"),
+                                                          R(16), T(2), C(white), S(3) * velocity, DT(12), 1, 0.5f); break;
+          case $(188) ImSpinner::SpinnerPointsArcBounce  (Name("SpinnerPointsArcBounce3"),
+                                                          R(16), T(2), C(white), S(3) * velocity, DT(12), 2, 0.3f); break;
+          case $(189) ImSpinner::SpinnerPointsArcBounce  (Name("SpinnerPointsArcBounce4"),
+                                                          R(16), T(2), C(white), S(3) * velocity, DT(12), 3, 0.3f); break;
+          case $(190) ImSpinner::SpinnerTwinBlocks       (Name("SpinnerTwinBlocks"),
+                                                          R(16), T(7), C(ImColor(255, 255, 255, 30)), CB(ImColor::HSV(hue * 0.005f, 0.8f, 0.8f)), S(5) * velocity); break;
+
+          }
+#undef $
+        }
+        ImGui::PopID();
+      };
+
+      if( ImGui::BeginTable("Demo table", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV) )
+      {
+        ImGui::TableNextColumn(); // Grid
+        {
+          // Extra 'Child region' needed here, to make scrollable-area
+          if(ImGui::BeginChild("Grid"))
+          {
+            ImGuiStyle& style = ImGui::GetStyle();
+
+            // Store previous Item spacing & Window padding (to restore it later)
+            const ImVec2 prevSpacing = style.ItemSpacing;
+            const ImVec2 prevPadding = style.WindowPadding;
+
+            // Set Item spacing & Window padding as zero
+            style.ItemSpacing = style.WindowPadding = {0.f, 0.f};
+
+            // -----------------------------------------------------------------
+            // For drawing spinners used 'Row-wrap' layout, same as in
+            // Dear ImGui Demo > Layout > Basic Horizontal Layout > Manual wrapping:
+            //   https://github.com/ocornut/imgui/blob/1029f57b8aa9118d08413d1d8a6dd9d32cf0d5f1/imgui_demo.cpp#L2866-L2878
+
+            const float region_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetColumnWidth();
+
+            const ImVec2 item_size = ImVec2(widget_size, widget_size);
+            for(int current_spi = 0; current_spi < num_spinners; current_spi++)
+            {
+              // BeginChild here needed to restrict item width&height by specific size
+              if( ImGui::BeginChild(100 + current_spi, item_size, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NavFlattened) )
+              {
+                  draw_spinner(current_spi, widget_size);
+              }
+              ImGui::EndChild();
+
+              // Show tooltip over spinner
+              if( ImGui::IsItemHovered() )
+              {
+                //if( )
+                ImGui::BeginTooltip();
+                {
+                  // Number
+                  ImGui::TextDisabled("%04u", current_spi);
+
+                  // Spinner name
+                  if(__nn.count(current_spi)) {
+                      ImGui::SameLine();
+                      ImGui::Text(" - %s", __nn[current_spi] );
+                  }
+
+                  ImGui::EndTooltip();
+                }
+              }
+
+              // -------------------------------------------------------------
+
+              const float last_item_x2 = ImGui::GetItemRectMax().x;
+              const float next_item_x2 = last_item_x2 + style.ItemSpacing.x + item_size.x; // Expected position if next item was on same line
+              if ((current_spi + 1 < num_spinners) && (next_item_x2 < region_visible_x2)) {
+                ImGui::SameLine();
+              }
+            }
+
+            // -----------------------------------------------------------------
+
+            // Restore previous Item spacing & Window padding
+            style.ItemSpacing   = prevSpacing;
+            style.WindowPadding = prevPadding;
+          }
+          ImGui::EndChild();
+        }
+
+        // ---------------------------------------------------------------------
+
+        ImGui::TableNextColumn(); // Options
+        {
+          ImGui::SliderFloat("Velocity", &velocity, 0.0f, 10.0f, "velocity = %.2f");
+          ImGui::Checkbox("Show Numbers", &show_number);
+          ImGui::SliderFloat("Grid size", &widget_size, 0.0f, 100.0f, "size = %.2f");
+
+          // -----------------------------------------------------------------
+          // Spinner-related parameters
+
+          constexpr ImGuiColorEditFlags COLOR_EDIT_FLAGS =
+            ImGuiColorEditFlags_PickerHueWheel |
+            ImGuiColorEditFlags_NoSidePreview  |
+            ImGuiColorEditFlags_NoInputs       |
+            ImGuiColorEditFlags_NoAlpha;
+
+          if(__nn.count(last_cci)) ImGui::Separator();
+
+          if (__rr.count(last_cci)) ImGui::SliderFloat("Radius", &__rr[last_cci], 0.0f, 100.0f, "radius = %.2f");
+          if (__tt.count(last_cci)) ImGui::SliderFloat("Thickness", &__tt[last_cci], 0.0f, 100.0f, "thickness = %.2f");
+          if (__cc.count(last_cci)) {
+          ImGui::Checkbox("Change Color", &__hc[last_cci]);
+          if (__hc[last_cci]) { __cc[last_cci] = ImColor::HSV(hue * 0.005f, 0.8f, 0.8f); }
+          else {
+              ImGui::SameLine(); ImGui::SetNextItemWidth(120);
+              ImGui::ColorPicker3("##MyColor", (float *)&__cc[last_cci], COLOR_EDIT_FLAGS);
+          }
+          }
+          if (__cb.count(last_cci)) {
+              ImGui::Checkbox("Change Bg Color", &__hcb[last_cci]);
+              if (__hcb[last_cci]) { __cb[last_cci] = ImColor::HSV(hue * 0.008f, 0.8f, 0.8f); }
+              else {
+                  ImGui::SameLine(); ImGui::SetNextItemWidth(120);
+                  ImGui::ColorPicker3("##MyBgColor", (float *)&__cb[last_cci], COLOR_EDIT_FLAGS);
+              }
+          }
+          if (__ss.count(last_cci)) ImGui::SliderFloat("Speed", &__ss[last_cci], 0.0f, 100.0f, "speed = %.2f");
+          if (__aa.count(last_cci)) ImGui::SliderFloat("Angle", &__aa[last_cci], 0.0f, PI_2, "angle = %.2f");
+          if (__amn.count(last_cci)) ImGui::SliderFloat("Angle Min", &__amn[last_cci], 0.0f, PI_2, "angle min = %.2f");
+          if (__amx.count(last_cci)) ImGui::SliderFloat("Angle Max", &__amx[last_cci], 0.0f, PI_2, "angle max = %.2f");
+          if (__dt.count(last_cci)) ImGui::SliderInt("Dots", &__dt[last_cci], 1, 100, "dots = %u");
+          if (__mdt.count(last_cci)) ImGui::SliderInt("MidDots", &__mdt[last_cci], 1, 100, "mid dots = %u");
+          if (__dd.count(last_cci)) ImGui::SliderFloat("Delta", &__dd[last_cci], -1.f, 1.f, "delta = %f");
+        }
+
+        ImGui::EndTable();
+      }
+    }
+#endif // IMSPINNER_DEMO
+}
+
+#endif // _IMSPINNER_H_

+ 2 - 2
Praxis3D/Data/config.ini

@@ -19,8 +19,8 @@ eye_adaption_intended_brightness 0.5
 mouse_captured 1
 mouse_captured 1
 mouse_warp_mode 0
 mouse_warp_mode 0
 fov 80
 fov 80
-z_near 0.1
-z_far 40000
+z_near 1.0
+z_far 40000.0
 default_map componentTest.pmap
 default_map componentTest.pmap
 atm_scattering_ground_frag_shader atmosphericScatteringPass_ground_simple.frag
 atm_scattering_ground_frag_shader atmosphericScatteringPass_ground_simple.frag
 lens_flare_ghost_threshold 2.0
 lens_flare_ghost_threshold 2.0

+ 3 - 1
Praxis3D/Data/error-strings-eng.data

@@ -48,8 +48,10 @@
 		"SDL_video_init_failed"							: "SDL Video has failed to initialize",
 		"SDL_video_init_failed"							: "SDL Video has failed to initialize",
 		"SDL_vsync_failed"									: "Failed to change vertical synchronization mode",
 		"SDL_vsync_failed"									: "Failed to change vertical synchronization mode",
 		"Window_creation_failed"						: "Unable to create a window",
 		"Window_creation_failed"						: "Unable to create a window",
+		"Duplicate_object_id"								: "Duplicate game object ID",
 		"Invalid_object_id"									: "Invalid game object ID",
 		"Invalid_object_id"									: "Invalid game object ID",
-		"Duplicate_object_id"								: "Duplicate game object ID"
+		"Nonexistent_object_id"							: "Entity ID does not exist",
+		"Nonexistent_parent_entity"					: "Parent entity does not exist"
 	},
 	},
 	"Error Sources":
 	"Error Sources":
 	{
 	{

+ 1 - 0
Praxis3D/Praxis3D.vcxproj

@@ -368,6 +368,7 @@
     <ClInclude Include="Source\ObjectRegister.h" />
     <ClInclude Include="Source\ObjectRegister.h" />
     <ClInclude Include="Source\Universal.h" />
     <ClInclude Include="Source\Universal.h" />
     <ClInclude Include="Source\Utilities.h" />
     <ClInclude Include="Source\Utilities.h" />
+    <ClInclude Include="Source\Version.h" />
     <ClInclude Include="Source\Window.h" />
     <ClInclude Include="Source\Window.h" />
     <ClInclude Include="Source\WindowLocator.h" />
     <ClInclude Include="Source\WindowLocator.h" />
     <ClInclude Include="Source\WorldEditObject.h" />
     <ClInclude Include="Source\WorldEditObject.h" />

+ 3 - 0
Praxis3D/Praxis3D.vcxproj.filters

@@ -938,6 +938,9 @@
     <ClInclude Include="..\Dependencies\include\ImGuizmo\ImGuizmo.h">
     <ClInclude Include="..\Dependencies\include\ImGuizmo\ImGuizmo.h">
       <Filter>3rd Party\ImGuizmo</Filter>
       <Filter>3rd Party\ImGuizmo</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Source\Version.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <None Include="Data\config.ini" />
     <None Include="Data\config.ini" />

+ 1 - 1
Praxis3D/Source/AtmScatteringPass.cpp

@@ -53,7 +53,7 @@ void AtmScatteringPass::InitModel()
 	constexpr double kMiePhaseFunctionG = 0.8;
 	constexpr double kMiePhaseFunctionG = 0.8;
 	constexpr double kGroundAlbedo = 0.1;
 	constexpr double kGroundAlbedo = 0.1;
 	const double max_sun_zenith_angle =
 	const double max_sun_zenith_angle =
-		(m_useHalfPrecision ? 102.0 : 120.0) / 180.0 * PI;
+		(m_useHalfPrecision ? 102.0 : 120.0) / 180.0 * Math::PI_DOUBLE;
 
 
 	DensityProfileLayer
 	DensityProfileLayer
 		rayleigh_layer(0.0, 1.0, -1.0 / kRayleighScaleHeight, 0.0, 0.0);
 		rayleigh_layer(0.0, 1.0, -1.0 / kRayleighScaleHeight, 0.0, 0.0);

+ 79 - 76
Praxis3D/Source/AtmScatteringPass.h

@@ -19,7 +19,7 @@ public:
 		kLengthUnitInMeters(1000.0),
 		kLengthUnitInMeters(1000.0),
 		m_atmParamBuffer(BufferType_Uniform, BufferBindTarget_Uniform, BufferUsageHint_DynamicDraw)
 		m_atmParamBuffer(BufferType_Uniform, BufferBindTarget_Uniform, BufferUsageHint_DynamicDraw)
 	{
 	{
-		kSunSolidAngle = PI * kSunAngularRadius * kSunAngularRadius;
+		kSunSolidAngle = Math::PI_DOUBLE * kSunAngularRadius * kSunAngularRadius;
 
 
 		m_vertexShaderSource = R"(
 		m_vertexShaderSource = R"(
 	#version 330
 	#version 330
@@ -128,82 +128,85 @@ public:
 	{
 	{
 		glEnable(GL_DEPTH_TEST);
 		glEnable(GL_DEPTH_TEST);
 
 
-		if(p_renderPassData.m_atmScatDoSkyPass)
+		if(p_sceneObjects.m_processDrawing)
 		{
 		{
-			//glDisable(GL_DEPTH_TEST);
-			//glEnable(GL_DEPTH_TEST);
-			glDepthFunc(GL_LEQUAL);
-			//glDepthMask(GL_FALSE);
-
-			// Bind output color texture for writing to
-			m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(p_renderPassData.getColorOutputMap());
-
-			glUseProgram(m_skyShader->getShaderHandle());
-
-			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Irradiance);
-			glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getIrradianceTexture());
-			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Scattering);
-			glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getScatteringTexture());
-			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_SingleMie);
-			glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getSingleMieTexture());
-			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Transmittance);
-			glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getTransmittanceTexture());
-
-			// Set atmosphere parameters buffer size and data
-			//m_atmParamBuffer.m_updateSize = sizeof(AtmScatteringParameters);
-			//m_atmParamBuffer.m_data = (void*)(&m_atmScatteringParam);
-			//m_renderer.queueForUpdate(m_atmParamBuffer);
-
-			// Pass update commands so they are executed 
-			//m_renderer.passUpdateCommandsToBackend();
-
-			// Perform various visual effects in the post process shader
-			m_renderer.queueForDrawing(m_skyShader->getShaderHandle(), m_skyShader->getUniformUpdater(), p_sceneObjects.m_cameraViewMatrix);
-			
-		}
-		else
-		{
-			glDepthFunc(GL_GREATER);
-			//glDisable(GL_DEPTH_TEST);
-			//glEnable(GL_DEPTH_TEST);
-			//glDepthFunc(GL_LEQUAL);
-			//glDepthMask(GL_FALSE);
-
-			// Bind output color texture for writing to
-			m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(p_renderPassData.getColorOutputMap());
-
-			glUseProgram(m_groundShader->getShaderHandle());
-
-			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Irradiance);
-			glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getIrradianceTexture());
-			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Scattering);
-			glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getScatteringTexture());
-			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_SingleMie);
-			glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getSingleMieTexture());
-			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Transmittance);
-			glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getTransmittanceTexture());
-			
-			
-			auto tex1 = m_atmScatteringModel->getIrradianceTexture();
-			auto tex2 = m_atmScatteringModel->getScatteringTexture();
-			auto tex3 = m_atmScatteringModel->getSingleMieTexture();
-			auto tex4 = m_atmScatteringModel->getTransmittanceTexture();
-
-			m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(p_renderPassData.getColorInputMap(), GBufferTextureType::GBufferInputTexture);
-			m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(GBufferTextureType::GBufferPosition, GBufferTextureType::GBufferPosition);
-			m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(GBufferTextureType::GBufferNormal, GBufferTextureType::GBufferNormal);
-			
-			// Set atmosphere parameters buffer size and data
-			//m_atmParamBuffer.m_updateSize = sizeof(AtmScatteringParameters);
-			//m_atmParamBuffer.m_data = (void*)(&m_atmScatteringParam);
-			//m_renderer.queueForUpdate(m_atmParamBuffer);
-
-			// Pass update commands so they are executed 
-			//m_renderer.passUpdateCommandsToBackend();
-
-			// Perform various visual effects in the post process shader
-			m_renderer.queueForDrawing(m_groundShader->getShaderHandle(), m_groundShader->getUniformUpdater(), p_sceneObjects.m_cameraViewMatrix);
-		
+			if(p_renderPassData.m_atmScatDoSkyPass)
+			{
+				//glDisable(GL_DEPTH_TEST);
+				//glEnable(GL_DEPTH_TEST);
+				glDepthFunc(GL_LEQUAL);
+				//glDepthMask(GL_FALSE);
+
+				// Bind output color texture for writing to
+				m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(p_renderPassData.getColorOutputMap());
+
+				glUseProgram(m_skyShader->getShaderHandle());
+
+				glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Irradiance);
+				glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getIrradianceTexture());
+				glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Scattering);
+				glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getScatteringTexture());
+				glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_SingleMie);
+				glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getSingleMieTexture());
+				glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Transmittance);
+				glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getTransmittanceTexture());
+
+				// Set atmosphere parameters buffer size and data
+				//m_atmParamBuffer.m_updateSize = sizeof(AtmScatteringParameters);
+				//m_atmParamBuffer.m_data = (void*)(&m_atmScatteringParam);
+				//m_renderer.queueForUpdate(m_atmParamBuffer);
+
+				// Pass update commands so they are executed 
+				//m_renderer.passUpdateCommandsToBackend();
+
+				// Perform various visual effects in the post process shader
+				m_renderer.queueForDrawing(m_skyShader->getShaderHandle(), m_skyShader->getUniformUpdater(), p_sceneObjects.m_cameraViewMatrix);
+
+			}
+			else
+			{
+				glDepthFunc(GL_GREATER);
+				//glDisable(GL_DEPTH_TEST);
+				//glEnable(GL_DEPTH_TEST);
+				//glDepthFunc(GL_LEQUAL);
+				//glDepthMask(GL_FALSE);
+
+				// Bind output color texture for writing to
+				m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(p_renderPassData.getColorOutputMap());
+
+				glUseProgram(m_groundShader->getShaderHandle());
+
+				glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Irradiance);
+				glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getIrradianceTexture());
+				glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Scattering);
+				glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getScatteringTexture());
+				glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_SingleMie);
+				glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getSingleMieTexture());
+				glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Transmittance);
+				glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getTransmittanceTexture());
+
+
+				auto tex1 = m_atmScatteringModel->getIrradianceTexture();
+				auto tex2 = m_atmScatteringModel->getScatteringTexture();
+				auto tex3 = m_atmScatteringModel->getSingleMieTexture();
+				auto tex4 = m_atmScatteringModel->getTransmittanceTexture();
+
+				m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(p_renderPassData.getColorInputMap(), GBufferTextureType::GBufferInputTexture);
+				m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(GBufferTextureType::GBufferPosition, GBufferTextureType::GBufferPosition);
+				m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(GBufferTextureType::GBufferNormal, GBufferTextureType::GBufferNormal);
+
+				// Set atmosphere parameters buffer size and data
+				//m_atmParamBuffer.m_updateSize = sizeof(AtmScatteringParameters);
+				//m_atmParamBuffer.m_data = (void*)(&m_atmScatteringParam);
+				//m_renderer.queueForUpdate(m_atmParamBuffer);
+
+				// Pass update commands so they are executed 
+				//m_renderer.passUpdateCommandsToBackend();
+
+				// Perform various visual effects in the post process shader
+				m_renderer.queueForDrawing(m_groundShader->getShaderHandle(), m_groundShader->getUniformUpdater(), p_sceneObjects.m_cameraViewMatrix);
+
+			}
 		}
 		}
 		
 		
 		// Pass the draw command so it is executed
 		// Pass the draw command so it is executed

+ 2 - 1
Praxis3D/Source/CommandBuffer.h

@@ -70,7 +70,8 @@ public:
 					p_object.m_materials[MaterialType_Diffuse][meshIndex].getHandle(),
 					p_object.m_materials[MaterialType_Diffuse][meshIndex].getHandle(),
 					p_object.m_materials[MaterialType_Normal][meshIndex].getHandle(),
 					p_object.m_materials[MaterialType_Normal][meshIndex].getHandle(),
 					p_object.m_materials[MaterialType_Emissive][meshIndex].getHandle(),
 					p_object.m_materials[MaterialType_Emissive][meshIndex].getHandle(),
-					p_object.m_materials[MaterialType_Combined][meshIndex].getHandle()))
+					p_object.m_materials[MaterialType_Combined][meshIndex].getHandle(),
+					p_object.m_baseObjectData.m_textureWrapMode))
 			);
 			);
 		}
 		}
 	}
 	}

+ 8 - 0
Praxis3D/Source/CommonDefinitions.h

@@ -236,6 +236,14 @@ enum TextureDataFormat : int
 	TextureDataFormat_R16UI		= GL_R16UI,
 	TextureDataFormat_R16UI		= GL_R16UI,
 	TextureDataFormat_R32UI		= GL_R32UI
 	TextureDataFormat_R32UI		= GL_R32UI
 };
 };
+enum TextureWrapType : int
+{
+	TextureWrapType_ClampToBorder		= GL_CLAMP_TO_BORDER,
+	TextureWrapType_ClampToEdge			= GL_CLAMP_TO_EDGE,
+	TextureWrapType_MirroredClampToEdge = GL_MIRROR_CLAMP_TO_EDGE,
+	TextureWrapType_MirroredRepeat		= GL_MIRRORED_REPEAT,
+	TextureWrapType_Repeat				= GL_REPEAT
+};
 enum UniformBufferBinding : unsigned int
 enum UniformBufferBinding : unsigned int
 {
 {
 	UniformBufferBinding_PointLights,
 	UniformBufferBinding_PointLights,

+ 4 - 1
Praxis3D/Source/Config.cpp

@@ -214,7 +214,10 @@ void Config::init()
 	AddVariablePredef(m_GUIVar, gui_file_dialog_min_size_y);
 	AddVariablePredef(m_GUIVar, gui_file_dialog_min_size_y);
 	AddVariablePredef(m_GUIVar, gui_file_dialog_dir_color_R);
 	AddVariablePredef(m_GUIVar, gui_file_dialog_dir_color_R);
 	AddVariablePredef(m_GUIVar, gui_file_dialog_dir_color_G);
 	AddVariablePredef(m_GUIVar, gui_file_dialog_dir_color_G);
-	AddVariablePredef(m_GUIVar, gui_file_dialog_dir_color_B); 
+	AddVariablePredef(m_GUIVar, gui_file_dialog_dir_color_B);
+	AddVariablePredef(m_GUIVar, loading_spinner_radius);
+	AddVariablePredef(m_GUIVar, loading_spinner_speed);
+	AddVariablePredef(m_GUIVar, loading_spinner_thickness);
 	AddVariablePredef(m_GUIVar, editor_button_add_texture);
 	AddVariablePredef(m_GUIVar, editor_button_add_texture);
 	AddVariablePredef(m_GUIVar, editor_button_add_list_texture);
 	AddVariablePredef(m_GUIVar, editor_button_add_list_texture);
 	AddVariablePredef(m_GUIVar, editor_button_arrow_down_texture);
 	AddVariablePredef(m_GUIVar, editor_button_arrow_down_texture);

+ 12 - 0
Praxis3D/Source/Config.h

@@ -359,6 +359,8 @@ namespace Properties
 	Code(Attenuation,) \
 	Code(Attenuation,) \
 	Code(Camera,) \
 	Code(Camera,) \
 	Code(CameraComponent,) \
 	Code(CameraComponent,) \
+	Code(ClampToBorder,) \
+	Code(ClampToEdge,) \
 	Code(Color,) \
 	Code(Color,) \
 	Code(CombinedTexture,) \
 	Code(CombinedTexture,) \
 	Code(ComputeShader,) \
 	Code(ComputeShader,) \
@@ -382,6 +384,8 @@ namespace Properties
 	Code(Materials,) \
 	Code(Materials,) \
 	Code(Metalness,) \
 	Code(Metalness,) \
 	Code(Meshes,) \
 	Code(Meshes,) \
+	Code(MirroredClampToEdge,) \
+	Code(MirroredRepeat,) \
 	Code(Models,) \
 	Code(Models,) \
 	Code(ModelComponent,) \
 	Code(ModelComponent,) \
 	Code(ModelObject,) \
 	Code(ModelObject,) \
@@ -400,6 +404,7 @@ namespace Properties
 	Code(Renderer,) \
 	Code(Renderer,) \
 	Code(Rendering,) \
 	Code(Rendering,) \
 	Code(RenderPasses,) \
 	Code(RenderPasses,) \
+	Code(Repeat,) \
 	Code(RMHAO,) \
 	Code(RMHAO,) \
 	Code(Roughness,) \
 	Code(Roughness,) \
 	Code(Shaders,) \
 	Code(Shaders,) \
@@ -415,6 +420,7 @@ namespace Properties
 	Code(TextureTilingFactor,) \
 	Code(TextureTilingFactor,) \
 	Code(TextureScale,) \
 	Code(TextureScale,) \
 	Code(VertexShader,) \
 	Code(VertexShader,) \
+	Code(WrapMode,) \
 	/* Graphics rendering passes */ \
 	/* Graphics rendering passes */ \
 	Code(AtmScatteringRenderPass,) \
 	Code(AtmScatteringRenderPass,) \
 	Code(BloomRenderPass,) \
 	Code(BloomRenderPass,) \
@@ -980,6 +986,9 @@ public:
 			gui_file_dialog_dir_color_R = 0.905f;
 			gui_file_dialog_dir_color_R = 0.905f;
 			gui_file_dialog_dir_color_G = 0.623f;
 			gui_file_dialog_dir_color_G = 0.623f;
 			gui_file_dialog_dir_color_B = 0.314f;
 			gui_file_dialog_dir_color_B = 0.314f;
+			loading_spinner_radius = 24.0f;
+			loading_spinner_speed = 16.0f;
+			loading_spinner_thickness = 3.0f;
 			editor_button_add_texture = "buttons\\button_add_3.png";
 			editor_button_add_texture = "buttons\\button_add_3.png";
 			editor_button_add_list_texture = "buttons\\button_add_from_list_1.png";
 			editor_button_add_list_texture = "buttons\\button_add_from_list_1.png";
 			editor_button_arrow_down_texture = "buttons\\button_arrow_down_1.png";
 			editor_button_arrow_down_texture = "buttons\\button_arrow_down_1.png";
@@ -1016,6 +1025,9 @@ public:
 		float gui_file_dialog_dir_color_R;
 		float gui_file_dialog_dir_color_R;
 		float gui_file_dialog_dir_color_G;
 		float gui_file_dialog_dir_color_G;
 		float gui_file_dialog_dir_color_B;
 		float gui_file_dialog_dir_color_B;
+		float loading_spinner_radius;
+		float loading_spinner_speed;
+		float loading_spinner_thickness;
 		std::string editor_button_add_texture;
 		std::string editor_button_add_texture;
 		std::string editor_button_add_list_texture;
 		std::string editor_button_add_list_texture;
 		std::string editor_button_arrow_down_texture;
 		std::string editor_button_arrow_down_texture;

+ 164 - 48
Praxis3D/Source/EditorWindow.cpp

@@ -1,10 +1,12 @@
 #include <glm/gtc/type_ptr.hpp>
 #include <glm/gtc/type_ptr.hpp>
 #include <ranges>
 #include <ranges>
 
 
+#define IMSPINNER_DEMO
 #include "EngineDefinitions.h"
 #include "EngineDefinitions.h"
 #include "EditorWindow.h"
 #include "EditorWindow.h"
 #include "imgui_internal.h"
 #include "imgui_internal.h"
 #include "ImGuizmo.h"
 #include "ImGuizmo.h"
+#include "imspinner.h"
 #include "RendererScene.h"
 #include "RendererScene.h"
 #include "ShaderUniformUpdater.h"
 #include "ShaderUniformUpdater.h"
 #include "WorldScene.h"
 #include "WorldScene.h"
@@ -85,9 +87,8 @@ void EditorWindow::update(const float p_deltaTime)
         m_pendingEntityToSelect = false;
         m_pendingEntityToSelect = false;
     }
     }
 
 
-    static float f = 0.0f;
-    static int counter = 0;
-    ImGui::ShowDemoWindow();
+    if(m_showImGuiDemoWindow)
+        ImGui::ShowDemoWindow();
     {
     {
         ImGuiStyle *style = &ImGui::GetStyle();
         ImGuiStyle *style = &ImGui::GetStyle();
         ImVec4 *colors = style->Colors;
         ImVec4 *colors = style->Colors;
@@ -274,13 +275,25 @@ void EditorWindow::update(const float p_deltaTime)
         }
         }
         if(ImGui::BeginMenu("Edit"))
         if(ImGui::BeginMenu("Edit"))
         {
         {
-            if(ImGui::MenuItem("Undo", "CTRL+Z")) {}
+            if(ImGui::MenuItem("Undo", "CTRL+Z", false, false)) {}  // Disabled item
             if(ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {}  // Disabled item
             if(ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {}  // Disabled item
             ImGui::Separator();
             ImGui::Separator();
             if(ImGui::MenuItem("Cut", "CTRL+X")) {}
             if(ImGui::MenuItem("Cut", "CTRL+X")) {}
             if(ImGui::MenuItem("Copy", "CTRL+C")) {}
             if(ImGui::MenuItem("Copy", "CTRL+C")) {}
             if(ImGui::MenuItem("Paste", "CTRL+V")) {}
             if(ImGui::MenuItem("Paste", "CTRL+V")) {}
             ImGui::EndMenu();
             ImGui::EndMenu();
+        }        
+        if(ImGui::BeginMenu("Debug"))
+        {
+            if(ImGui::MenuItem("Show ImGui demo window", "", m_showImGuiDemoWindow)) 
+            {
+                m_showImGuiDemoWindow = !m_showImGuiDemoWindow;
+            }
+            if(ImGui::MenuItem("Show Imspinner demo window", "", m_showImspinnerDemoWindow))
+            {
+                m_showImspinnerDemoWindow = !m_showImspinnerDemoWindow;
+            }
+            ImGui::EndMenu();
         }
         }
 
 
         // Process the pressed main menu button
         // Process the pressed main menu button
@@ -500,8 +513,8 @@ void EditorWindow::update(const float p_deltaTime)
                             m_newEntityWindowInitialized = true;
                             m_newEntityWindowInitialized = true;
                             m_mousePositionOnNewEntity = ImGui::GetMousePos();
                             m_mousePositionOnNewEntity = ImGui::GetMousePos();
 
 
-                            // Assign a next available entity ID
-                            EntityID newEntityID = 1;
+                            // Assign a next available entity ID (if the new entity has a non-root parent, start the available ID search from the next ID after the parent)
+                            EntityID newEntityID = m_newEntityConstructionInfo->m_parent == 0 ? 1 : m_newEntityConstructionInfo->m_parent + 1;
                             for(decltype(m_entityList.size()) i = 0, size = m_entityList.size(); i < size; i++)
                             for(decltype(m_entityList.size()) i = 0, size = m_entityList.size(); i < size; i++)
                             {
                             {
                                 if(newEntityID == m_entityList[i].m_entityID)
                                 if(newEntityID == m_entityList[i].m_entityID)
@@ -926,41 +939,102 @@ void EditorWindow::update(const float p_deltaTime)
                                     m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Generic::Active);
                                     m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Generic::Active);
                                 }
                                 }
 
 
-                                // Draw POSITION
-                                drawLeftAlignedLabelText("Position:", inputWidgetOffset);
-                                if(ImGui::DragFloat3("##PositionDrag", glm::value_ptr(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_spatialData.m_position), Config::GUIVar().editor_float_slider_speed))
+                                if(ImGui::BeginTabBar("##RightWindowTabBar", ImGuiTabBarFlags_None))
                                 {
                                 {
-                                    // If the position vector was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
-                                    if(rigidBodyComponent != nullptr)
-                                        m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalPosition);
-                                    else
-                                        m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::LocalPosition);
-                                }
+                                    if(ImGui::BeginTabItem("Local"))
+                                    {
+                                        // Draw POSITION
+                                        drawLeftAlignedLabelText("Position:", inputWidgetOffset);
+                                        if(ImGui::DragFloat3("##LocalPositionDrag", glm::value_ptr(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_spatialData.m_position), Config::GUIVar().editor_float_slider_speed))
+                                        {
+                                            // If the position vector was changed, set the new position in the spatial data manager (so it can set the appropriate dirty flags internally)
+                                            m_selectedEntity.m_spatialDataManager.setLocalPosition(m_selectedEntity.m_spatialDataManager.getLocalSpaceData().m_spatialData.m_position);
+                                            // Update the spatial data manager (so it updates the transform matrix internally)
+                                            m_selectedEntity.m_spatialDataManager.update();
+
+                                            // If the position vector was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
+                                            if(rigidBodyComponent != nullptr)
+                                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalTransformNoScale);
+                                            else
+                                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::LocalTransformNoScale);
+                                        }
 
 
-                                // Draw ROTATION
-                                // Make sure to get the current local rotation euler angles, as they are not automatically updated
-                                m_selectedEntity.m_spatialDataManager.calculateLocalRotationEuler();
-                                drawLeftAlignedLabelText("Rotation:", inputWidgetOffset);
-                                if(ImGui::DragFloat3("##RotationDrag", glm::value_ptr(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_spatialData.m_rotationEuler), Config::GUIVar().editor_float_slider_speed))
-                                {
-                                    // If the rotation vector was changed, set the new rotation in the spatial data manager (so it can set the appropriate dirty flags internally)
-                                    m_selectedEntity.m_spatialDataManager.setLocalRotation(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_spatialData.m_rotationEuler);
-                                    // Update the spatial data manager (so it updates the rotation quaternion internally)
-                                    m_selectedEntity.m_spatialDataManager.update();
-
-                                    // If the rotation vector was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
-                                    if(rigidBodyComponent != nullptr)
-                                        m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalRotation);
-                                    else
-                                        m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::LocalRotation);
-                                }
+                                        // Draw ROTATION
+                                        // Make sure to get the current local rotation euler angles, as they are not automatically updated
+                                        m_selectedEntity.m_spatialDataManager.calculateLocalRotationEuler();
+                                        drawLeftAlignedLabelText("Rotation:", inputWidgetOffset);
+                                        if(ImGui::DragFloat3("##LocalRotationDrag", glm::value_ptr(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_spatialData.m_rotationEuler), Config::GUIVar().editor_float_slider_speed))
+                                        {
+                                            // If the rotation vector was changed, set the new rotation in the spatial data manager (so it can set the appropriate dirty flags internally)
+                                            m_selectedEntity.m_spatialDataManager.setLocalRotation(m_selectedEntity.m_spatialDataManager.getLocalSpaceData().m_spatialData.m_rotationEuler);
+                                            // Update the spatial data manager (so it updates the transform matrix internally)
+                                            m_selectedEntity.m_spatialDataManager.update();
+
+                                            // If the rotation vector was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
+                                            if(rigidBodyComponent != nullptr)
+                                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalTransformNoScale);
+                                            else
+                                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::LocalTransformNoScale);
+                                        }
 
 
-                                // Draw SCALE
-                                drawLeftAlignedLabelText("Scale:", inputWidgetOffset);
-                                if(ImGui::DragFloat3("##ScaleDrag", glm::value_ptr(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_spatialData.m_scale), Config::GUIVar().editor_float_slider_speed, 0.01f, 10000.0f))
-                                {
-                                    // If the scale vector was changed, send a notification to the spatial component
-                                    m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::LocalScale);
+                                        // Draw SCALE
+                                        drawLeftAlignedLabelText("Scale:", inputWidgetOffset);
+                                        if(ImGui::DragFloat3("##LocalScaleDrag", glm::value_ptr(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_spatialData.m_scale), Config::GUIVar().editor_float_slider_speed, 0.01f, 10000.0f))
+                                        {
+                                            // If the scale vector was changed, send a notification to the spatial component
+                                            m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::LocalScale);
+                                        }
+                                        ImGui::EndTabItem();
+                                    }
+
+                                    // World spatial data is read-only, i.e. only used for viewing the data, as there is no useful need for modifying it
+                                    if(ImGui::BeginTabItem("World"))
+                                    {
+                                        auto worldTransformNoScale = m_selectedEntity.m_spatialDataManager.getWorldTransformWithScale();
+                                        auto rotationEuler = glm::degrees(glm::eulerAngles(glm::toQuat(worldTransformNoScale)));
+
+                                        // Draw POSITION
+                                        drawLeftAlignedLabelText("Position:", inputWidgetOffset);
+                                        if(ImGui::DragFloat3("##WorldPositionDrag", glm::value_ptr(worldTransformNoScale[3]), Config::GUIVar().editor_float_slider_speed))
+                                        {
+                                            /*/ If the position vector was changed, set the new world transform in the spatial data manager (so it can set the appropriate dirty flags internally)
+                                            m_selectedEntity.m_spatialDataManager.setWorldTransform(worldTransformNoScale);
+                                            // Update the spatial data manager (so it updates the transform matrix internally)
+                                            m_selectedEntity.m_spatialDataManager.update();
+
+                                            // If the position vector was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
+                                            if(rigidBodyComponent != nullptr)
+                                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::WorldTransformNoScale);
+                                            else
+                                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::WorldTransformNoScale);*/
+                                        }
+
+                                        // Draw ROTATION
+                                        drawLeftAlignedLabelText("Rotation:", inputWidgetOffset);
+                                        if(ImGui::DragFloat3("##WorldRotationDrag", glm::value_ptr(rotationEuler), Config::GUIVar().editor_float_slider_speed))
+                                        {
+                                            /*/ If the rotation vector was changed, set the new world transform in the spatial data manager (so it can set the appropriate dirty flags internally)
+                                            m_selectedEntity.m_spatialDataManager.setWorldTransform(Math::createTransformMat(worldTransformNoScale[3], Math::eulerDegreesToQuaterion(rotationEuler)));
+                                            // Update the spatial data manager (so it updates the transform matrix internally)
+                                            m_selectedEntity.m_spatialDataManager.update();
+
+                                            // If the rotation vector was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
+                                            if(rigidBodyComponent != nullptr)
+                                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::WorldTransformNoScale);
+                                            else
+                                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::WorldTransformNoScale);*/
+                                        }
+
+                                        // Draw SCALE
+                                        drawLeftAlignedLabelText("Scale:", inputWidgetOffset);
+                                        if(ImGui::DragFloat3("##WorldScaleDrag", glm::value_ptr(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_spatialData.m_scale), Config::GUIVar().editor_float_slider_speed, 0.01f, 10000.0f))
+                                        {
+                                            // If the scale vector was changed, send a notification to the spatial component
+                                            //m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::LocalScale);
+                                        }
+                                        ImGui::EndTabItem();
+                                    }
+                                    ImGui::EndTabBar();
                                 }
                                 }
                             }
                             }
                         }
                         }
@@ -1394,6 +1468,28 @@ void EditorWindow::update(const float p_deltaTime)
                                                         m_selectedEntity.m_modelDataModified = true;
                                                         m_selectedEntity.m_modelDataModified = true;
                                                     }
                                                     }
 
 
+                                                    // Get the current texture wrap mode
+                                                    int textureWrapMode = 0;
+                                                    for(decltype(m_textureWrapModeTypes.size()) i = 0, size = m_textureWrapModeTypes.size(); i < size; i++)
+                                                    {
+                                                        if(m_textureWrapModeTypes[i] == modelEntry.m_textureWrapMode[meshIndex])
+                                                        {
+                                                            textureWrapMode = (int)i;
+                                                            break;
+                                                        }
+                                                    }
+
+                                                    // Draw TEXTURE WRAP MODE
+                                                    drawLeftAlignedLabelText("Texture wrap mode:", inputWidgetOffset);
+                                                    if(ImGui::Combo(("##" + Utilities::toString(modelIndex) + Utilities::toString(meshIndex) + "TextureWrapModeCombo").c_str(), &textureWrapMode, &m_textureWrapModeStrings[0], (int)m_textureWrapModeStrings.size()))
+                                                    {
+                                                        // Set the texture wrap mode
+                                                        modelEntry.m_textureWrapMode[meshIndex] = m_textureWrapModeTypes[textureWrapMode];
+
+                                                        // Set the modified flag
+                                                        m_selectedEntity.m_modelDataModified = true;
+                                                    }
+
                                                     for(unsigned int materialIndex = 0; materialIndex < MaterialType::MaterialType_NumOfTypes; materialIndex++)
                                                     for(unsigned int materialIndex = 0; materialIndex < MaterialType::MaterialType_NumOfTypes; materialIndex++)
                                                     {
                                                     {
                                                         // Convert material type to text
                                                         // Convert material type to text
@@ -1555,6 +1651,11 @@ void EditorWindow::update(const float p_deltaTime)
                                         m_selectedEntity.m_modelDataModified = true;
                                         m_selectedEntity.m_modelDataModified = true;
                                     }
                                     }
                                 }
                                 }
+                                else
+                                {
+                                    ImGui::SetCursorPosX((ImGui::GetWindowWidth() - 32.0f) / 2.0f);
+                                    ImSpinner::SpinnerFadeDots("##ModelLoadingSpinner", 32.0f, 4.0f, ImSpinner::white, 16.0f, 8);
+                                }
                             }
                             }
                         }
                         }
                         auto *shaderComponent = entityRegistry.try_get<ShaderComponent>(m_selectedEntity.m_entityID);
                         auto *shaderComponent = entityRegistry.try_get<ShaderComponent>(m_selectedEntity.m_entityID);
@@ -2753,11 +2854,11 @@ void EditorWindow::update(const float p_deltaTime)
                     //	|____________________________|
                     //	|____________________________|
                     //
                     //
                     // Get window starting position and the size of available space inside the window
                     // Get window starting position and the size of available space inside the window
-                    auto windowPosition = ImGui::GetCursorScreenPos();
-                    auto contentRegionSize = ImGui::GetContentRegionAvail();
+                    m_sceneViewportPosition = ImGui::GetCursorScreenPos();
+                    m_sceneViewportSize = ImGui::GetContentRegionAvail();
 
 
-                    m_centerWindowSize.x = (int)contentRegionSize.x;
-                    m_centerWindowSize.y = (int)contentRegionSize.y;
+                    m_centerWindowSize.x = (int)m_sceneViewportSize.x;
+                    m_centerWindowSize.y = (int)m_sceneViewportSize.y;
 
 
                     // Tell the renderer the size of available window space as a render to texture resolution, so that the rendered scene fill the whole window
                     // Tell the renderer the size of available window space as a render to texture resolution, so that the rendered scene fill the whole window
                     m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_RenderToTextureResolution, (void *)&m_centerWindowSize);
                     m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_RenderToTextureResolution, (void *)&m_centerWindowSize);
@@ -2778,7 +2879,7 @@ void EditorWindow::update(const float p_deltaTime)
                     ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
                     ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
                     ImGui::ImageButton(
                     ImGui::ImageButton(
                         (ImTextureID)sceneRenderBufferHandle,
                         (ImTextureID)sceneRenderBufferHandle,
-                        contentRegionSize,
+                        m_sceneViewportSize,
                         ImVec2(0, 1),
                         ImVec2(0, 1),
                         ImVec2(1, 0)
                         ImVec2(1, 0)
                     );
                     );
@@ -2793,7 +2894,7 @@ void EditorWindow::update(const float p_deltaTime)
                     ImGuizmo::SetDrawlist();
                     ImGuizmo::SetDrawlist();
 
 
                     // Set the screen size for ImGuizmo
                     // Set the screen size for ImGuizmo
-                    ImGuizmo::SetRect(windowPosition.x, windowPosition.y, contentRegionSize.x, contentRegionSize.y);
+                    ImGuizmo::SetRect(m_sceneViewportPosition.x, m_sceneViewportPosition.y, m_sceneViewportSize.x, m_sceneViewportSize.y);
 
 
                     // Draw ImGuizmo only if there's an entity selected
                     // Draw ImGuizmo only if there's an entity selected
                     if(m_selectedEntity)
                     if(m_selectedEntity)
@@ -2810,12 +2911,15 @@ void EditorWindow::update(const float p_deltaTime)
                             // Try to get the rigid body component for sending spatial changes
                             // Try to get the rigid body component for sending spatial changes
                             auto *rigidBodyComponent = entityRegistry.try_get<RigidBodyComponent>(m_selectedEntity.m_entityID);
                             auto *rigidBodyComponent = entityRegistry.try_get<RigidBodyComponent>(m_selectedEntity.m_entityID);
 
 
-                            // Get the current local transform matrix of the selected entity
-                            m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_transformMatNoScale = spatialComponent->getSpatialDataChangeManager().getLocalTransform();
+                            // Get the current world transform matrix of the selected entity
+                            glm::mat4 worldMatrix = spatialComponent->getSpatialDataChangeManager().getWorldTransform();
 
 
                             // Draw TRANSLATE MANIPULATION ImGuizmo
                             // Draw TRANSLATE MANIPULATION ImGuizmo
-                            if(m_translateGuizmoEnabled && ImGuizmo::Manipulate(glm::value_ptr(rendererScene->getViewMatrix()[0]), glm::value_ptr(rendererScene->getProjectionMatrix()[0]), ImGuizmo::OPERATION::TRANSLATE, ImGuizmo::MODE::LOCAL, glm::value_ptr(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_transformMatNoScale[0])))
+                            if(m_translateGuizmoEnabled && ImGuizmo::Manipulate(glm::value_ptr(rendererScene->getViewMatrix()[0]), glm::value_ptr(rendererScene->getProjectionMatrix()[0]), ImGuizmo::OPERATION::TRANSLATE, ImGuizmo::MODE::WORLD, glm::value_ptr(worldMatrix[0])))
                             {
                             {
+                                // Get the local transform from the world matrix by multiplying it with the inverse of the parent transform
+                                m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_transformMatNoScale = glm::inverse(spatialComponent->getSpatialDataChangeManager().getParentTransform()) * worldMatrix;
+
                                 // If the model transform matrix was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
                                 // If the model transform matrix was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
                                 if(rigidBodyComponent != nullptr)
                                 if(rigidBodyComponent != nullptr)
                                     m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalTransformNoScale);
                                     m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalTransformNoScale);
@@ -2824,8 +2928,11 @@ void EditorWindow::update(const float p_deltaTime)
                             }
                             }
 
 
                             // Draw ROTATE MANIPULATION ImGuizmo
                             // Draw ROTATE MANIPULATION ImGuizmo
-                            if(m_rotateGuizmoEnabled && ImGuizmo::Manipulate(glm::value_ptr(rendererScene->getViewMatrix()[0]), glm::value_ptr(rendererScene->getProjectionMatrix()[0]), ImGuizmo::OPERATION::ROTATE, ImGuizmo::MODE::LOCAL, glm::value_ptr(m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_transformMatNoScale[0])))
+                            if(m_rotateGuizmoEnabled && ImGuizmo::Manipulate(glm::value_ptr(rendererScene->getViewMatrix()[0]), glm::value_ptr(rendererScene->getProjectionMatrix()[0]), ImGuizmo::OPERATION::ROTATE, ImGuizmo::MODE::LOCAL, glm::value_ptr(worldMatrix[0])))
                             {
                             {
+                                // Get the local transform from the world matrix by multiplying it with the inverse of the parent transform
+                                m_selectedEntity.m_spatialDataManager.getLocalSpaceDataNonConst().m_transformMatNoScale = glm::inverse(spatialComponent->getSpatialDataChangeManager().getParentTransform()) * worldMatrix;
+
                                 // If the model transform matrix was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
                                 // If the model transform matrix was changed, send a notification to the either the Spatial Component or Rigid Body Component (if the Rigid Body Component is present, it takes control over the spatial data)
                                 if(rigidBodyComponent != nullptr)
                                 if(rigidBodyComponent != nullptr)
                                     m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalTransformNoScale);
                                     m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalTransformNoScale);
@@ -2974,6 +3081,15 @@ void EditorWindow::update(const float p_deltaTime)
                     }
                     }
                 }
                 }
 
 
+                if(m_showImspinnerDemoWindow)
+                {
+                    if(ImGui::BeginTabItem("Demo spinners", 0, m_textureInspectorTabFlags))
+                    {
+                        ImSpinner::demoSpinners();
+                        ImGui::EndTabItem();
+                    }
+                }
+
                 ImGui::EndTabBar();
                 ImGui::EndTabBar();
             }
             }
         }
         }

+ 29 - 22
Praxis3D/Source/EditorWindow.h

@@ -28,6 +28,8 @@ public:
 		m_translateGuizmoEnabled = true;
 		m_translateGuizmoEnabled = true;
 		m_rotateGuizmoEnabled = false;
 		m_rotateGuizmoEnabled = false;
 		m_showNewMapWindow = false;
 		m_showNewMapWindow = false;
+		m_showImGuiDemoWindow = false;
+		m_showImspinnerDemoWindow = false;
 		m_activatedMainMenuButton = MainMenuButtonType::MainMenuButtonType_None;
 		m_activatedMainMenuButton = MainMenuButtonType::MainMenuButtonType_None;
 		m_sceneState = EditorSceneState::EditorSceneState_Pause;
 		m_sceneState = EditorSceneState::EditorSceneState_Pause;
 		m_centerWindowSize = glm::ivec2(0);
 		m_centerWindowSize = glm::ivec2(0);
@@ -50,20 +52,9 @@ public:
 		m_currentlyOpenedFileBrowser = FileBrowserActivated::FileBrowserActivated_None;
 		m_currentlyOpenedFileBrowser = FileBrowserActivated::FileBrowserActivated_None;
 		m_fileBrowserDialog.m_name = "EditorFileBrowserDialog";
 		m_fileBrowserDialog.m_name = "EditorFileBrowserDialog";
 
 
-		m_luaVariableTypeStrings = { "null", "bool", "int", "float", "double", "vec2i", "vec2f", "vec3f", "vec4f", "string", "propertyID" };
-
-		m_shaderTypeStrings = { "Compute", "Fragment", "Geometry", "Vertex", "Tessellation control", "Tessellation evaluation" };
 		m_selectedProgramShader = -1;
 		m_selectedProgramShader = -1;
 		m_selectedShader = -1;
 		m_selectedShader = -1;
 
 
-		for(unsigned int i = 0; i < ObjectMaterialType::NumberOfMaterialTypes; i++)
-			m_physicalMaterialProperties.push_back(GetString(static_cast<ObjectMaterialType>(i)));
-
-		for(unsigned int i = 0; i < RenderPassType::RenderPassType_NumOfTypes; i++)
-			m_renderingPassesTypeText.push_back(GetString(static_cast<RenderPassType>(i)));
-
-		m_tonemappingMethodText = { "None", "Simple reinhard", "Reinhard with white point", "Filmic tonemapping", "Uncharted 2", "Unreal 3", "ACES", "Lottes", "Uchimura" };
-
 		m_nextEntityToSelect = NULL_ENTITY_ID;
 		m_nextEntityToSelect = NULL_ENTITY_ID;
 		m_pendingEntityToSelect = false;
 		m_pendingEntityToSelect = false;
 
 
@@ -81,6 +72,20 @@ public:
 		m_buttonBackgroundDisabled = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
 		m_buttonBackgroundDisabled = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
 		m_mousePositionOnNewEntity = ImVec2(0.0f, 0.0f);
 		m_mousePositionOnNewEntity = ImVec2(0.0f, 0.0f);
 		m_newEntityWindowInitialized = false;
 		m_newEntityWindowInitialized = false;
+		m_sceneViewportPosition = ImVec2(0.0f, 0.0f);
+		m_sceneViewportSize = ImVec2(0.0f, 0.0f);
+
+		for(unsigned int i = 0; i < ObjectMaterialType::NumberOfMaterialTypes; i++)
+			m_physicalMaterialProperties.push_back(GetString(static_cast<ObjectMaterialType>(i)));
+
+		for(unsigned int i = 0; i < RenderPassType::RenderPassType_NumOfTypes; i++)
+			m_renderingPassesTypeText.push_back(GetString(static_cast<RenderPassType>(i)));
+
+		m_luaVariableTypeStrings = { "null", "bool", "int", "float", "double", "vec2i", "vec2f", "vec3f", "vec4f", "string", "propertyID" };
+		m_shaderTypeStrings = { "Compute", "Fragment", "Geometry", "Vertex", "Tessellation control", "Tessellation evaluation" };
+		m_tonemappingMethodText = { "None", "Simple reinhard", "Reinhard with white point", "Filmic tonemapping", "Uncharted 2", "Unreal 3", "ACES", "Lottes", "Uchimura" };
+		m_textureWrapModeStrings = { "Clamp to border", "Clamp to edge", "Mirrored clamp to edge", "Mirrored repeat", "Repeat" };
+		m_textureWrapModeTypes = { TextureWrapType::TextureWrapType_ClampToBorder, TextureWrapType::TextureWrapType_ClampToEdge, TextureWrapType::TextureWrapType_MirroredClampToEdge, TextureWrapType::TextureWrapType_MirroredRepeat, TextureWrapType::TextureWrapType_Repeat };
 	}
 	}
 	~EditorWindow();
 	~EditorWindow();
 
 
@@ -908,20 +913,17 @@ private:
 		return "";
 		return "";
 	}
 	}
 
 
-	bool show_demo_window;
-	bool show_another_window;
-	float clear_color;
-
 	bool m_renderSceneToTexture;
 	bool m_renderSceneToTexture;
 	bool m_GUISequenceEnabled;
 	bool m_GUISequenceEnabled;
 	bool m_LUAScriptingEnabled;
 	bool m_LUAScriptingEnabled;
 	bool m_translateGuizmoEnabled;
 	bool m_translateGuizmoEnabled;
 	bool m_rotateGuizmoEnabled;
 	bool m_rotateGuizmoEnabled;
 	bool m_showNewMapWindow;
 	bool m_showNewMapWindow;
+	bool m_showImGuiDemoWindow;
+	bool m_showImspinnerDemoWindow;
 	MainMenuButtonType m_activatedMainMenuButton;
 	MainMenuButtonType m_activatedMainMenuButton;
 	EditorSceneState m_sceneState;
 	EditorSceneState m_sceneState;
 	glm::ivec2 m_centerWindowSize;
 	glm::ivec2 m_centerWindowSize;
-	std::vector<const char *> m_physicalMaterialProperties;
 	KeyCommand m_keys[KeyType::KeyType_NumOfKeys];
 	KeyCommand m_keys[KeyType::KeyType_NumOfKeys];
 
 
 	// GUI settings
 	// GUI settings
@@ -936,12 +938,10 @@ private:
 	ImVec4 m_buttonBackgroundDisabled;
 	ImVec4 m_buttonBackgroundDisabled;
 	ImVec2 m_mousePositionOnNewEntity;
 	ImVec2 m_mousePositionOnNewEntity;
 	bool m_newEntityWindowInitialized;
 	bool m_newEntityWindowInitialized;
-
-	// LUA variables editor data
-	std::vector<const char *> m_luaVariableTypeStrings;
+	ImVec2 m_sceneViewportPosition;
+	ImVec2 m_sceneViewportSize;
 
 
 	// Assets variables
 	// Assets variables
-	std::vector<const char *> m_shaderTypeStrings;
 	std::vector<std::pair<const Texture2D *, std::string>> m_textureAssets;
 	std::vector<std::pair<const Texture2D *, std::string>> m_textureAssets;
 	std::vector<std::pair<const Model *, std::string>> m_modelAssets;
 	std::vector<std::pair<const Model *, std::string>> m_modelAssets;
 	std::vector<std::pair<const ShaderLoader::ShaderProgram *, std::string>> m_shaderAssets;
 	std::vector<std::pair<const ShaderLoader::ShaderProgram *, std::string>> m_shaderAssets;
@@ -974,8 +974,6 @@ private:
 	// New scene settings
 	// New scene settings
 	SceneData m_newSceneData;
 	SceneData m_newSceneData;
 	ImGuiTabItemFlags m_newSceneSettingsTabFlags;
 	ImGuiTabItemFlags m_newSceneSettingsTabFlags;
-	std::vector<const char *> m_renderingPassesTypeText;
-	std::vector<const char *> m_tonemappingMethodText;
 
 
 	// Button textures
 	// Button textures
 	std::vector<TextureLoader2D::Texture2DHandle> m_buttonTextures;
 	std::vector<TextureLoader2D::Texture2DHandle> m_buttonTextures;
@@ -986,4 +984,13 @@ private:
 
 
 	// Square button size that is the same height as text
 	// Square button size that is the same height as text
 	ImVec2 m_buttonSizedByFont;
 	ImVec2 m_buttonSizedByFont;
+
+	// String arrays and other data used for ImGui Combo inputs
+	std::vector<const char *> m_physicalMaterialProperties;
+	std::vector<const char *> m_renderingPassesTypeText;
+	std::vector<const char *> m_luaVariableTypeStrings;
+	std::vector<const char *> m_shaderTypeStrings;
+	std::vector<const char *> m_tonemappingMethodText;
+	std::vector<const char *> m_textureWrapModeStrings;
+	std::vector<TextureWrapType> m_textureWrapModeTypes;
 };
 };

+ 1 - 0
Praxis3D/Source/ErrorCodes.h

@@ -98,6 +98,7 @@ DECLARE_ENUM(ErrorType, ERROR_TYPES)
 	Code(Duplicate_object_id,) \
 	Code(Duplicate_object_id,) \
 	Code(Invalid_object_id,) \
 	Code(Invalid_object_id,) \
 	Code(Nonexistent_object_id,) \
 	Code(Nonexistent_object_id,) \
+	Code(Nonexistent_parent_entity,) \
 	/* Error management */ \
 	/* Error management */ \
 	Code(NumberOfErrorCodes,) \
 	Code(NumberOfErrorCodes,) \
 	Code(CachedError,)
 	Code(CachedError,)

+ 1 - 0
Praxis3D/Source/ErrorHandler.cpp

@@ -71,6 +71,7 @@ ErrorHandler::ErrorHandler()
 	AssignErrorType(Duplicate_object_id, Warning);
 	AssignErrorType(Duplicate_object_id, Warning);
 	AssignErrorType(Invalid_object_id, Warning);
 	AssignErrorType(Invalid_object_id, Warning);
 	AssignErrorType(Nonexistent_object_id, Warning);
 	AssignErrorType(Nonexistent_object_id, Warning);
+	AssignErrorType(Nonexistent_parent_entity, Warning);
 
 
 	// Add error sources to the hash map, and offset them by number of error codes, because they share the same hash map
 	// Add error sources to the hash map, and offset them by number of error codes, because they share the same hash map
 	for(unsigned int i = 0; i < Source_NumberOfErrorSources; i++)
 	for(unsigned int i = 0; i < Source_NumberOfErrorSources; i++)

+ 44 - 0
Praxis3D/Source/GUIScene.cpp

@@ -7,6 +7,7 @@
 #include "EditorWindow.h"
 #include "EditorWindow.h"
 #include "GUIHandlerLocator.h"
 #include "GUIHandlerLocator.h"
 #include "GUIScene.h"
 #include "GUIScene.h"
+#include "imspinner.h"
 #include "NullSystemObjects.h"
 #include "NullSystemObjects.h"
 #include "TaskManagerLocator.h"
 #include "TaskManagerLocator.h"
 
 
@@ -87,6 +88,49 @@ void GUIScene::update(const float p_deltaTime)
 		// Set the beginning of the GUI frame
 		// Set the beginning of the GUI frame
 		GUIHandlerLocator::get().beginFrame();
 		GUIHandlerLocator::get().beginFrame();
 
 
+		//	 ____________________________
+		//	|							 |
+		//	|	   LOADING STATUS		 |
+		//	|____________________________|
+		//
+		// Check if any of the scenes are currently loading
+		bool loadingStatus = false;
+		for(unsigned int i = 0; i < Systems::TypeID::NumberOfSystems; i++)
+			if(m_sceneLoader->getSystemScene(static_cast<Systems::TypeID>(i))->getLoadingStatus())
+				loadingStatus = true;
+
+		// If any scenes are currently loading
+		if(loadingStatus)
+		{
+			auto &io = ImGui::GetIO();
+			ImVec2 sceneViewportCenter;
+
+			// If editor window is present, center the loading status to editor's scene viewport,
+			// otherwise center the loading status to the whole screen
+			if(m_editorWindow != nullptr)
+				sceneViewportCenter = m_editorWindow->m_sceneViewportSize * 0.5f + m_editorWindow->m_sceneViewportPosition;
+			else
+				sceneViewportCenter = ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
+
+			// Center the loading status window
+			ImGui::SetNextWindowPos(sceneViewportCenter, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
+
+			// Draw the loading status window with no title bar and transparent background
+			if(ImGui::Begin("##LoadingStatusWindow", (bool *)0, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_AlwaysAutoResize))
+			{
+				// Draw the loading spinner
+				ImSpinner::SpinnerAng("##ModelLoadingSpinner", Config::GUIVar().loading_spinner_radius, Config::GUIVar().loading_spinner_thickness, ImSpinner::white, ImColor(255, 255, 255, 0), Config::GUIVar().loading_spinner_speed);
+
+				if(m_editorWindow == nullptr)
+				{
+					ImGui::NewLine();
+					ImGui::Text("Loading");
+				}
+
+			}
+			ImGui::End();
+		}
+
 		//	 ____________________________
 		//	 ____________________________
 		//	|							 |
 		//	|							 |
 		//	|	 FILE BROWSER DIALOGS	 |
 		//	|	 FILE BROWSER DIALOGS	 |

+ 36 - 54
Praxis3D/Source/GeometryPass.h

@@ -67,77 +67,59 @@ public:
 		// Initialize the geometry framebuffer for the geometry pass
 		// Initialize the geometry framebuffer for the geometry pass
 		m_renderer.m_backend.getGeometryBuffer()->initGeometryPass();
 		m_renderer.m_backend.getGeometryBuffer()->initGeometryPass();
 
 
-		// Get known shader details
-		auto geomShaderHandle = m_shaderGeometry->getShaderHandle();
-		auto &geomUniformUpdater = m_shaderGeometry->getUniformUpdater();
-
-		// Iterate over all objects to be rendered with geometry shader
-		for(auto entity : p_sceneObjects.m_models)
+		if(p_sceneObjects.m_processDrawing)
 		{
 		{
-			ModelComponent &model = p_sceneObjects.m_models.get<ModelComponent>(entity);
-			if(model.isObjectActive())
-			{
-				SpatialComponent &spatialData = p_sceneObjects.m_models.get<SpatialComponent>(entity);
-				auto &modelData = model.getModelData();
-
-				for(decltype(modelData.size()) i = 0, size = modelData.size(); i < size; i++)
-				{
-					m_renderer.queueForDrawing(modelData[i],
-						geomShaderHandle,
-						geomUniformUpdater,
-						spatialData.getSpatialDataChangeManager().getWorldTransformWithScale(),
-						m_renderer.m_viewProjMatrix);
-				}
-			}
-		}
+			// Get known shader details
+			auto geomShaderHandle = m_shaderGeometry->getShaderHandle();
+			auto &geomUniformUpdater = m_shaderGeometry->getUniformUpdater();
 
 
-		// Iterate over all objects to be rendered with a custom shader
-		for(auto entity : p_sceneObjects.m_modelsWithShaders)
-		{
-			ModelComponent &model = p_sceneObjects.m_modelsWithShaders.get<ModelComponent>(entity);
-			if(model.isObjectActive())
+			// Iterate over all objects to be rendered with geometry shader
+			for(auto entity : p_sceneObjects.m_models)
 			{
 			{
-				SpatialComponent &spatialData = p_sceneObjects.m_modelsWithShaders.get<SpatialComponent>(entity);
-				ShaderComponent &shader = p_sceneObjects.m_modelsWithShaders.get<ShaderComponent>(entity);
-				if(shader.isLoadedToVideoMemory())
+				ModelComponent &model = p_sceneObjects.m_models.get<ModelComponent>(entity);
+				if(model.isObjectActive())
 				{
 				{
+					SpatialComponent &spatialData = p_sceneObjects.m_models.get<SpatialComponent>(entity);
 					auto &modelData = model.getModelData();
 					auto &modelData = model.getModelData();
 
 
 					for(decltype(modelData.size()) i = 0, size = modelData.size(); i < size; i++)
 					for(decltype(modelData.size()) i = 0, size = modelData.size(); i < size; i++)
 					{
 					{
 						m_renderer.queueForDrawing(modelData[i],
 						m_renderer.queueForDrawing(modelData[i],
-							shader.getShaderData()->m_shader.getShaderHandle(),
-							shader.getShaderData()->m_shader.getUniformUpdater(),
+							geomShaderHandle,
+							geomUniformUpdater,
 							spatialData.getSpatialDataChangeManager().getWorldTransformWithScale(),
 							spatialData.getSpatialDataChangeManager().getWorldTransformWithScale(),
 							m_renderer.m_viewProjMatrix);
 							m_renderer.m_viewProjMatrix);
 					}
 					}
 				}
 				}
 			}
 			}
-		}
-
 
 
-		/*/ Iterate over all objects to be rendered with geometry shader
-		for(decltype(p_sceneObjects.m_modelData.size()) objIndex = 0, numObjects = p_sceneObjects.m_modelData.size(); objIndex < numObjects; objIndex++)
-		{
-			m_renderer.queueForDrawing(p_sceneObjects.m_modelData[objIndex].m_modelData,
-								geomShaderHandle, 
-								geomUniformUpdater,
-								p_sceneObjects.m_modelData[objIndex].m_modelMatrix,
+			// Iterate over all objects to be rendered with a custom shader
+			for(auto entity : p_sceneObjects.m_modelsWithShaders)
+			{
+				ModelComponent &model = p_sceneObjects.m_modelsWithShaders.get<ModelComponent>(entity);
+				if(model.isObjectActive())
+				{
+					SpatialComponent &spatialData = p_sceneObjects.m_modelsWithShaders.get<SpatialComponent>(entity);
+					ShaderComponent &shader = p_sceneObjects.m_modelsWithShaders.get<ShaderComponent>(entity);
+					if(shader.isLoadedToVideoMemory())
+					{
+						auto &modelData = model.getModelData();
+
+						for(decltype(modelData.size()) i = 0, size = modelData.size(); i < size; i++)
+						{
+							m_renderer.queueForDrawing(modelData[i],
+								shader.getShaderData()->m_shader.getShaderHandle(),
+								shader.getShaderData()->m_shader.getUniformUpdater(),
+								spatialData.getSpatialDataChangeManager().getWorldTransformWithScale(),
 								m_renderer.m_viewProjMatrix);
 								m_renderer.m_viewProjMatrix);
-		}*/
+						}
+					}
+				}
+			}
 
 
-		/*/ Iterate over all objects to be rendered with a custom shader
-		for(decltype(p_sceneObjects.m_modelDataWithShaders.size()) objIndex = 0, numObjects = p_sceneObjects.m_modelDataWithShaders.size(); objIndex < numObjects; objIndex++)
-		{
-			m_renderer.queueForDrawing(p_sceneObjects.m_modelDataWithShaders[objIndex].m_modelData,
-				p_sceneObjects.m_modelDataWithShaders[objIndex].m_shader->getShaderHandle(),
-				p_sceneObjects.m_modelDataWithShaders[objIndex].m_shader->getUniformUpdater(),
-				p_sceneObjects.m_modelDataWithShaders[objIndex].m_modelMatrix,
-				m_renderer.m_viewProjMatrix);
-		}*/
-
-		// Pass all the draw commands to be executed
-		m_renderer.passDrawCommandsToBackend();
+			// Pass all the draw commands to be executed
+			m_renderer.passDrawCommandsToBackend();
+		}
 	}
 	}
 	
 	
 private:
 private:

+ 13 - 4
Praxis3D/Source/GraphicsDataSets.h

@@ -27,11 +27,12 @@ struct MaterialData
 // Contains data of a single mesh and its materials
 // Contains data of a single mesh and its materials
 struct MeshData
 struct MeshData
 {
 {
-	MeshData(const Model::Mesh &p_mesh, MaterialData p_materials[MaterialType::MaterialType_NumOfTypes], const float p_heightScale, const float p_alphaThreshold, const float p_emissiveIntensity, const bool p_active = true) :
+	MeshData(const Model::Mesh &p_mesh, MaterialData p_materials[MaterialType::MaterialType_NumOfTypes], const float p_heightScale, const float p_alphaThreshold, const float p_emissiveIntensity, const TextureWrapType p_textureWrapMode, const bool p_active = true) :
 		m_mesh(&p_mesh), 
 		m_mesh(&p_mesh), 
 		m_heightScale(p_heightScale),
 		m_heightScale(p_heightScale),
 		m_alphaThreshold(p_alphaThreshold),
 		m_alphaThreshold(p_alphaThreshold),
 		m_emissiveIntensity(p_emissiveIntensity),
 		m_emissiveIntensity(p_emissiveIntensity),
+		m_textureWrapMode(p_textureWrapMode),
 		m_active(p_active)
 		m_active(p_active)
 	{
 	{
 		std::copy(p_materials, p_materials + MaterialType::MaterialType_NumOfTypes, m_materials);
 		std::copy(p_materials, p_materials + MaterialType::MaterialType_NumOfTypes, m_materials);
@@ -45,6 +46,8 @@ struct MeshData
 	float m_emissiveIntensity;
 	float m_emissiveIntensity;
 	// Flag denoting whether to draw the mesh
 	// Flag denoting whether to draw the mesh
 	bool m_active;
 	bool m_active;
+	// Texture wrap mode
+	TextureWrapType m_textureWrapMode;
 
 
 	// Handle to a mesh
 	// Handle to a mesh
 	const Model::Mesh *m_mesh;
 	const Model::Mesh *m_mesh;
@@ -107,7 +110,7 @@ struct CameraData
 // All graphics objects contain an instance of this struct, which holds the necessary spacial and other data
 // All graphics objects contain an instance of this struct, which holds the necessary spacial and other data
 struct GraphicsData
 struct GraphicsData
 {
 {
-	GraphicsData() : m_scale(1.0f, 1.0f, 1.0f), m_alphaThreshold(0.0f), m_emissiveThreshold(0.0f), m_heightScale(0.0f), m_textureTilingFactor(1.0f) { }
+	GraphicsData() : m_scale(1.0f, 1.0f, 1.0f), m_textureWrapMode(GL_REPEAT), m_alphaThreshold(0.0f), m_emissiveThreshold(0.0f), m_heightScale(0.0f), m_textureTilingFactor(1.0f) { }
 
 
 	glm::vec3 m_position,
 	glm::vec3 m_position,
 				m_rotation,
 				m_rotation,
@@ -117,6 +120,8 @@ struct GraphicsData
 
 
 	glm::mat4 m_modelMat;
 	glm::mat4 m_modelMat;
 
 
+	int m_textureWrapMode;
+
 	float	m_alphaThreshold,
 	float	m_alphaThreshold,
 			m_emissiveThreshold,
 			m_emissiveThreshold,
 			m_heightScale,
 			m_heightScale,
@@ -326,7 +331,9 @@ struct PointLightDataSet
 		m_color(p_color), 
 		m_color(p_color), 
 		m_position(p_position), 
 		m_position(p_position), 
 		m_attenuation(p_attenuation), 
 		m_attenuation(p_attenuation), 
-		m_intensity(p_intensity) { }
+		m_intensity(p_intensity),
+		m_padding1(0.0f),
+		m_padding2(0.0f) { }
 
 
 	glm::vec3 m_color;
 	glm::vec3 m_color;
 	float m_padding1;	// Unused variable, declared for padding
 	float m_padding1;	// Unused variable, declared for padding
@@ -349,7 +356,9 @@ struct SpotLightDataSet
 		m_direction(p_direction),
 		m_direction(p_direction),
 		m_attenuation(p_attenuation), 
 		m_attenuation(p_attenuation), 
 		m_intensity(p_intensity),
 		m_intensity(p_intensity),
-		m_cutoffAngle(p_cutoffAngle) { }
+		m_cutoffAngle(p_cutoffAngle),
+		m_padding1(0.0f),
+		m_padding2(0.0f) { }
 	
 	
 	glm::vec3 m_color;
 	glm::vec3 m_color;
 	float m_padding1;	// Unused variable, declared for padding
 	float m_padding1;	// Unused variable, declared for padding

+ 30 - 15
Praxis3D/Source/Math.h

@@ -16,24 +16,39 @@
 #include <glm/gtx/quaternion.hpp>
 #include <glm/gtx/quaternion.hpp>
 #include <limits>
 #include <limits>
 
 
-#define E_CONST		2.71828182845904523536
-#define LOG2E		1.44269504088896340736
-#define LOG10E		0.434294481903251827651
-#define LN2			0.693147180559945309417
-#define LN10		2.30258509299404568402
-#define PI			3.14159265358979323846
-#define PI_2		1.57079632679489661923
-#define PI_4		0.785398163397448309616
-#define SQRTPI		1.12837916709551257390
-#define SQRT2		1.41421356237309504880
-#define SQRT1_2		0.707106781186547524401
-#define RAD			(PI / 180.0)
-#define DEG			(180.0 / PI)
-#define TWOPI		(PI * 2)
-#define RAD2DEG		(360/(PI*2))
 
 
 namespace Math
 namespace Math
 {
 {
+	constexpr float E_CONST			= 2.71828182845904523536f;
+	constexpr float LOG2E			= 1.44269504088896340736f;
+	constexpr float LOG10E			= 0.434294481903251827651f;
+	constexpr float LN2				= 0.693147180559945309417f;
+	constexpr float LN10			= 2.30258509299404568402f;
+	constexpr float PI				= 3.14159265358979323846f;
+	constexpr float PI_2			= 1.57079632679489661923f;
+	constexpr float PI_4			= 0.785398163397448309616f;
+	constexpr float SQRTPI			= 1.12837916709551257390f;
+	constexpr float SQRT2			= 1.41421356237309504880f;
+	constexpr float SQRT1_2			= 0.707106781186547524401f;
+	constexpr float RAD				= (PI / 180.0f);
+	constexpr float DEG				= (180.0f / PI);
+	constexpr float RAD2DEG			= (360.0f / (PI * 2.0f));
+
+	constexpr double E_CONST_DOUBLE = 2.71828182845904523536;
+	constexpr double LOG2E_DOUBLE	= 1.44269504088896340736;
+	constexpr double LOG10E_DOUBLE	= 0.434294481903251827651;
+	constexpr double LN2_DOUBLE		= 0.693147180559945309417;
+	constexpr double LN10_DOUBLE	= 2.30258509299404568402;
+	constexpr double PI_DOUBLE		= 3.14159265358979323846;
+	constexpr double PI_2_DOUBLE	= 1.57079632679489661923;
+	constexpr double PI_4_DOUBLE	= 0.785398163397448309616;
+	constexpr double SQRTPI_DOUBLE	= 1.12837916709551257390;
+	constexpr double SQRT2_DOUBLE	= 1.41421356237309504880;
+	constexpr double SQRT1_2_DOUBLE = 0.707106781186547524401;
+	constexpr double RAD_DOUBLE		= (PI_DOUBLE / 180.0);
+	constexpr double DEG_DOUBLE		= (180.0 / PI_DOUBLE);
+	constexpr double RAD2DEG_DOUBLE = (360.0 / (PI_DOUBLE * 2.0));
+
 	const glm::mat4 createTransformMat(const glm::vec3 &p_position, const glm::vec3 &p_rotation, const glm::vec3 &p_scale) noexcept;
 	const glm::mat4 createTransformMat(const glm::vec3 &p_position, const glm::vec3 &p_rotation, const glm::vec3 &p_scale) noexcept;
 	const inline glm::mat4 createTransformMat(const glm::vec3 &p_position, const glm::quat &p_rotation) noexcept
 	const inline glm::mat4 createTransformMat(const glm::vec3 &p_position, const glm::quat &p_rotation) noexcept
 	{
 	{

+ 16 - 3
Praxis3D/Source/ModelComponent.h

@@ -55,6 +55,10 @@ public:
 				// Resize the "mesh is present" array and initialize each element to false
 				// Resize the "mesh is present" array and initialize each element to false
 				if(p_size > m_present.size())
 				if(p_size > m_present.size())
 					m_present.resize(p_size, false);
 					m_present.resize(p_size, false);
+
+				// Resize the texture wrap mode array and initialize each element to Repeat
+				if(p_size > m_textureWrapMode.size())
+					m_textureWrapMode.resize(p_size, TextureWrapType::TextureWrapType_Repeat);
 			}
 			}
 		}
 		}
 
 
@@ -69,6 +73,7 @@ public:
 			m_heightScale.clear();
 			m_heightScale.clear();
 			m_active.clear();
 			m_active.clear();
 			m_present.clear();
 			m_present.clear();
+			m_textureWrapMode.clear();
 		}
 		}
 
 
 		std::string m_modelName;
 		std::string m_modelName;
@@ -82,6 +87,7 @@ public:
 		std::vector<float> m_heightScale;
 		std::vector<float> m_heightScale;
 		std::vector<bool> m_active;
 		std::vector<bool> m_active;
 		std::vector<bool> m_present;
 		std::vector<bool> m_present;
+		std::vector<TextureWrapType> m_textureWrapMode;
 	};
 	};
 	struct ModelsProperties
 	struct ModelsProperties
 	{
 	{
@@ -172,7 +178,8 @@ public:
 									materials, 
 									materials, 
 									m_modelsProperties->m_models[modelIndex].m_heightScale[meshIndex], 
 									m_modelsProperties->m_models[modelIndex].m_heightScale[meshIndex], 
 									m_modelsProperties->m_models[modelIndex].m_alphaThreshold[meshIndex], 
 									m_modelsProperties->m_models[modelIndex].m_alphaThreshold[meshIndex], 
-									m_modelsProperties->m_models[modelIndex].m_emissiveIntensity[meshIndex]));
+									m_modelsProperties->m_models[modelIndex].m_emissiveIntensity[meshIndex],
+									m_modelsProperties->m_models[modelIndex].m_textureWrapMode[meshIndex]));
 
 
 								ErrHandlerLoc::get().log(ErrorCode::Load_to_memory_success, ErrorSource::Source_ModelComponent, m_modelsProperties->m_models[modelIndex].m_modelName);
 								ErrHandlerLoc::get().log(ErrorCode::Load_to_memory_success, ErrorSource::Source_ModelComponent, m_modelsProperties->m_models[modelIndex].m_modelName);
 							}
 							}
@@ -193,7 +200,7 @@ public:
 								materials[iMatType].m_textureScale = glm::vec2(Config::graphicsVar().texture_tiling_factor, Config::graphicsVar().texture_tiling_factor);
 								materials[iMatType].m_textureScale = glm::vec2(Config::graphicsVar().texture_tiling_factor, Config::graphicsVar().texture_tiling_factor);
 							}
 							}
 
 
-							newModelData.m_meshes.push_back(MeshData(newModelData.m_model.getMeshArray()[meshIndex], materials, Config::graphicsVar().height_scale, Config::graphicsVar().alpha_threshold, Config::graphicsVar().emissive_multiplier));
+							newModelData.m_meshes.push_back(MeshData(newModelData.m_model.getMeshArray()[meshIndex], materials, Config::graphicsVar().height_scale, Config::graphicsVar().alpha_threshold, Config::graphicsVar().emissive_multiplier, TextureWrapType::TextureWrapType_Repeat));
 
 
 							ErrHandlerLoc::get().log(ErrorCode::Load_to_memory_success, ErrorSource::Source_ModelComponent, m_modelsProperties->m_models[modelIndex].m_modelName);
 							ErrHandlerLoc::get().log(ErrorCode::Load_to_memory_success, ErrorSource::Source_ModelComponent, m_modelsProperties->m_models[modelIndex].m_modelName);
 						}
 						}
@@ -294,6 +301,10 @@ public:
 						if(m_modelsProperties->m_models[modelIndex].m_emissiveIntensity.size() > meshIndex)
 						if(m_modelsProperties->m_models[modelIndex].m_emissiveIntensity.size() > meshIndex)
 							emissiveIntensity = m_modelsProperties->m_models[modelIndex].m_emissiveIntensity[meshIndex];
 							emissiveIntensity = m_modelsProperties->m_models[modelIndex].m_emissiveIntensity[meshIndex];
 
 
+						TextureWrapType textureWrapMode = TextureWrapType::TextureWrapType_Repeat;
+						if(m_modelsProperties->m_models[modelIndex].m_textureWrapMode.size() > meshIndex)
+							textureWrapMode = m_modelsProperties->m_models[modelIndex].m_textureWrapMode[meshIndex];
+
 						// Add the data for this mesh. Include materials loaded from the model itself, if they were present, otherwise, include default textures instead
 						// Add the data for this mesh. Include materials loaded from the model itself, if they were present, otherwise, include default textures instead
 						newModelData.m_meshes.push_back(MeshData(
 						newModelData.m_meshes.push_back(MeshData(
 							newModelData.m_model.getMeshArray()[meshIndex],
 							newModelData.m_model.getMeshArray()[meshIndex],
@@ -301,6 +312,7 @@ public:
 							heightScale,
 							heightScale,
 							alphaThreshold,
 							alphaThreshold,
 							emissiveIntensity,
 							emissiveIntensity,
+							textureWrapMode,
 							active));
 							active));
 					}
 					}
 					else
 					else
@@ -321,7 +333,7 @@ public:
 							materials[iMatType].m_textureScale = glm::vec2(Config::graphicsVar().texture_tiling_factor, Config::graphicsVar().texture_tiling_factor);
 							materials[iMatType].m_textureScale = glm::vec2(Config::graphicsVar().texture_tiling_factor, Config::graphicsVar().texture_tiling_factor);
 						}
 						}
 
 
-						newModelData.m_meshes.push_back(MeshData(newModelData.m_model.getMeshArray()[meshIndex], materials, Config::graphicsVar().height_scale, Config::graphicsVar().alpha_threshold, Config::graphicsVar().emissive_multiplier, true));
+						newModelData.m_meshes.push_back(MeshData(newModelData.m_model.getMeshArray()[meshIndex], materials, Config::graphicsVar().height_scale, Config::graphicsVar().alpha_threshold, Config::graphicsVar().emissive_multiplier, TextureWrapType::TextureWrapType_Repeat, true));
 
 
 						//ErrHandlerLoc::get().log(ErrorCode::Load_to_memory_success, ErrorSource::Source_ModelComponent, m_modelsProperties->m_models[modelIndex].m_modelName);
 						//ErrHandlerLoc::get().log(ErrorCode::Load_to_memory_success, ErrorSource::Source_ModelComponent, m_modelsProperties->m_models[modelIndex].m_modelName);
 					}
 					}
@@ -519,6 +531,7 @@ public:
 				newModelEntry.m_heightScale.push_back(m_modelData[modelIndex].m_meshes[meshIndex].m_heightScale);
 				newModelEntry.m_heightScale.push_back(m_modelData[modelIndex].m_meshes[meshIndex].m_heightScale);
 				newModelEntry.m_active.push_back(m_modelData[modelIndex].m_meshes[meshIndex].m_active);
 				newModelEntry.m_active.push_back(m_modelData[modelIndex].m_meshes[meshIndex].m_active);
 				newModelEntry.m_present.push_back(materialPresent);
 				newModelEntry.m_present.push_back(materialPresent);
+				newModelEntry.m_textureWrapMode.push_back(m_modelData[modelIndex].m_meshes[meshIndex].m_textureWrapMode);
 				newModelEntry.m_numOfMeshes++;
 				newModelEntry.m_numOfMeshes++;
 			}
 			}
 		}
 		}

+ 8 - 0
Praxis3D/Source/RendererBackend.cpp

@@ -107,15 +107,23 @@ void RendererBackend::processDrawing(const DrawCommands &p_drawCommands, const U
 		// Bind textures
 		// Bind textures
 		glActiveTexture(GL_TEXTURE0 + MaterialType_Diffuse);
 		glActiveTexture(GL_TEXTURE0 + MaterialType_Diffuse);
 		glBindTexture(GL_TEXTURE_2D, p_drawCommands[i].second.m_matDiffuse);
 		glBindTexture(GL_TEXTURE_2D, p_drawCommands[i].second.m_matDiffuse);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, p_drawCommands[i].second.m_matWrapMode);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, p_drawCommands[i].second.m_matWrapMode);
 
 
 		glActiveTexture(GL_TEXTURE0 + MaterialType_Normal);
 		glActiveTexture(GL_TEXTURE0 + MaterialType_Normal);
 		glBindTexture(GL_TEXTURE_2D, p_drawCommands[i].second.m_matNormal);
 		glBindTexture(GL_TEXTURE_2D, p_drawCommands[i].second.m_matNormal);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, p_drawCommands[i].second.m_matWrapMode);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, p_drawCommands[i].second.m_matWrapMode);
 
 
 		glActiveTexture(GL_TEXTURE0 + MaterialType_Combined);
 		glActiveTexture(GL_TEXTURE0 + MaterialType_Combined);
 		glBindTexture(GL_TEXTURE_2D, p_drawCommands[i].second.m_matCombined);
 		glBindTexture(GL_TEXTURE_2D, p_drawCommands[i].second.m_matCombined);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, p_drawCommands[i].second.m_matWrapMode);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, p_drawCommands[i].second.m_matWrapMode);
 
 
 		glActiveTexture(GL_TEXTURE0 + MaterialType_Emissive);
 		glActiveTexture(GL_TEXTURE0 + MaterialType_Emissive);
 		glBindTexture(GL_TEXTURE_2D, p_drawCommands[i].second.m_matEmissive);
 		glBindTexture(GL_TEXTURE_2D, p_drawCommands[i].second.m_matEmissive);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, p_drawCommands[i].second.m_matWrapMode);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, p_drawCommands[i].second.m_matWrapMode);
 		
 		
 		// Draw the geometry
 		// Draw the geometry
 		glDrawElementsBaseVertex(GL_TRIANGLES,
 		glDrawElementsBaseVertex(GL_TRIANGLES,

+ 303 - 299
Praxis3D/Source/RendererBackend.h

@@ -60,7 +60,8 @@ public:
 					const unsigned int p_matDiffuse,
 					const unsigned int p_matDiffuse,
 					const unsigned int p_matNormal,
 					const unsigned int p_matNormal,
 					const unsigned int p_matEmissive,
 					const unsigned int p_matEmissive,
-					const unsigned int p_matCombined) :
+					const unsigned int p_matCombined,
+					const int p_matWrapMode) :
 
 
 			m_uniformUpdater(p_uniformUpdater), 
 			m_uniformUpdater(p_uniformUpdater), 
 			m_uniformObjectData(p_uniformObjectData),
 			m_uniformObjectData(p_uniformObjectData),
@@ -72,7 +73,8 @@ public:
 			m_matDiffuse(p_matDiffuse),
 			m_matDiffuse(p_matDiffuse),
 			m_matNormal(p_matNormal),
 			m_matNormal(p_matNormal),
 			m_matEmissive(p_matEmissive),
 			m_matEmissive(p_matEmissive),
-			m_matCombined(p_matCombined) { }
+			m_matCombined(p_matCombined),
+			m_matWrapMode(p_matWrapMode) { }
 
 
 		const ShaderUniformUpdater &m_uniformUpdater;
 		const ShaderUniformUpdater &m_uniformUpdater;
 		const UniformObjectData m_uniformObjectData;
 		const UniformObjectData m_uniformObjectData;
@@ -84,6 +86,8 @@ public:
 		const unsigned int m_baseVertex;
 		const unsigned int m_baseVertex;
 		const unsigned int m_baseIndex;
 		const unsigned int m_baseIndex;
 
 
+		const int m_matWrapMode;
+
 		const unsigned int m_matDiffuse;
 		const unsigned int m_matDiffuse;
 		const unsigned int m_matNormal;
 		const unsigned int m_matNormal;
 		const unsigned int m_matEmissive;
 		const unsigned int m_matEmissive;
@@ -628,352 +632,352 @@ protected:
 	{
 	{
 		switch(p_command.m_objectType)
 		switch(p_command.m_objectType)
 		{
 		{
-		case LoadObject_Buffer:
-		{
-			// Create the buffer
-			glGenBuffers(1, &p_command.m_handle);
-
-			// Bind the buffer
-			glBindBuffer(p_command.m_objectData.m_bufferData.m_bufferType, p_command.m_handle);
-
-			// Fill the buffer
-			glBufferData(p_command.m_objectData.m_bufferData.m_bufferType, 
-						 p_command.m_objectData.m_bufferData.m_size, 
-						 p_command.m_objectData.m_bufferData.m_data,
-						 p_command.m_objectData.m_bufferData.m_bufferUsage);
-
-			// Bind the buffer to the binding index so it can be accessed in a shader
-			glBindBufferBase(p_command.m_objectData.m_bufferData.m_bufferBindTarget, 
-							 p_command.m_objectData.m_bufferData.m_bindingIndex,
-							 p_command.m_handle);
-		}
-		break;
-
-		case LoadObject_Shader:
-		{
-			unsigned int shaderHandles[ShaderType_NumOfTypes] = {};
-			unsigned int shaderTypes[ShaderType_NumOfTypes] = {
-				GL_COMPUTE_SHADER,
-				GL_FRAGMENT_SHADER,
-				GL_GEOMETRY_SHADER,
-				GL_VERTEX_SHADER,
-				GL_TESS_CONTROL_SHADER,
-				GL_TESS_EVALUATION_SHADER };
-
-			// Clear error queue (TODO: remove in the future)
-			glGetError();
-
-			// Create shader program handle
-			p_command.m_handle = glCreateProgram();
-
-			// Check for errors
-			GLenum glError = glGetError();
-			if(glError != GL_NO_ERROR)
-			{
-				// Log an error with every shader info log
-				for(unsigned int i = 0; i < ShaderType_NumOfTypes; i++)
+			case LoadObject_Buffer:
 				{
 				{
-					p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorCode = ErrorCode::Shader_creation_failed;
-					p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorSource = ErrorSource::Source_ShaderLoader;
+					// Create the buffer
+					glGenBuffers(1, &p_command.m_handle);
+
+					// Bind the buffer
+					glBindBuffer(p_command.m_objectData.m_bufferData.m_bufferType, p_command.m_handle);
+
+					// Fill the buffer
+					glBufferData(p_command.m_objectData.m_bufferData.m_bufferType,
+						p_command.m_objectData.m_bufferData.m_size,
+						p_command.m_objectData.m_bufferData.m_data,
+						p_command.m_objectData.m_bufferData.m_bufferUsage);
+
+					// Bind the buffer to the binding index so it can be accessed in a shader
+					glBindBufferBase(p_command.m_objectData.m_bufferData.m_bufferBindTarget,
+						p_command.m_objectData.m_bufferData.m_bindingIndex,
+						p_command.m_handle);
 				}
 				}
+				break;
 
 
-				std::string combinedNames;
-
-				// For every shader filename, if it's not empty, add it to the combined filename
-				for(unsigned int i = 0; i < ShaderType_NumOfTypes; i++)
-					if(!p_command.m_objectData.m_shaderData.m_names[i].empty())
-						combinedNames += p_command.m_objectData.m_shaderData.m_names[i] + ", ";
-				
-				// Remove the last 2 characters from the filename (comma and space)
-				if(!combinedNames.empty())
-				{
-					combinedNames.pop_back();
-					combinedNames.pop_back();
-				}
-
-				// Log an error with the error handler
-				ErrHandlerLoc::get().log(ErrorCode::Shader_creation_failed,
-										 ErrorSource::Source_ShaderLoader,
-										 "\"" + combinedNames + "\":\n" + Utilities::toString(glError));
-			}
-			else
-			{
-				unsigned int numOfShaders = ShaderType::ShaderType_NumOfTypes;
-
-				// If a compute shader exists, then load ONLY the compute shader
-				if(!p_command.m_objectData.m_shaderData.m_source[ShaderType::ShaderType_Compute].empty())
-					numOfShaders = 1;
-
-				// Create individual shaders
-				for(unsigned int i = 0; i < numOfShaders; i++)
+			case LoadObject_Shader:
 				{
 				{
-					if(!p_command.m_objectData.m_shaderData.m_source[i].empty())
+					unsigned int shaderHandles[ShaderType_NumOfTypes] = {};
+					unsigned int shaderTypes[ShaderType_NumOfTypes] = {
+						GL_COMPUTE_SHADER,
+						GL_FRAGMENT_SHADER,
+						GL_GEOMETRY_SHADER,
+						GL_VERTEX_SHADER,
+						GL_TESS_CONTROL_SHADER,
+						GL_TESS_EVALUATION_SHADER };
+
+					// Clear error queue (TODO: remove in the future)
+					glGetError();
+
+					// Create shader program handle
+					p_command.m_handle = glCreateProgram();
+
+					// Check for errors
+					GLenum glError = glGetError();
+					if(glError != GL_NO_ERROR)
 					{
 					{
-						// Create a shader handle
-						shaderHandles[i] = glCreateShader(shaderTypes[i]);
-
-						// Check for errors
-						glError = glGetError();
-						if(glError != GL_NO_ERROR)
+						// Log an error with every shader info log
+						for(unsigned int i = 0; i < ShaderType_NumOfTypes; i++)
 						{
 						{
-							// Log an error with a shader info log
 							p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorCode = ErrorCode::Shader_creation_failed;
 							p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorCode = ErrorCode::Shader_creation_failed;
 							p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorSource = ErrorSource::Source_ShaderLoader;
 							p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorSource = ErrorSource::Source_ShaderLoader;
-
-							// Log an error with the error handler
-							ErrHandlerLoc::get().log(ErrorCode::Shader_creation_failed,
-													 ErrorSource::Source_ShaderLoader,
-													 "\"" + p_command.m_objectData.m_shaderData.m_names[i] + "\":\n" + Utilities::toString(glError));
 						}
 						}
-						else
+
+						std::string combinedNames;
+
+						// For every shader filename, if it's not empty, add it to the combined filename
+						for(unsigned int i = 0; i < ShaderType_NumOfTypes; i++)
+							if(!p_command.m_objectData.m_shaderData.m_names[i].empty())
+								combinedNames += p_command.m_objectData.m_shaderData.m_names[i] + ", ";
+
+						// Remove the last 2 characters from the filename (comma and space)
+						if(!combinedNames.empty())
 						{
 						{
-							// Pass shader source code and compile it
-							const char *shaderSource = p_command.m_objectData.m_shaderData.m_source[i].c_str();
-							glShaderSource(shaderHandles[i], 1, &shaderSource, NULL);
-							glCompileShader(shaderHandles[i]);
-
-							// Check for shader compilation errors
-							GLint shaderCompileResult = 0;
-							glGetShaderiv(shaderHandles[i], GL_COMPILE_STATUS, &shaderCompileResult);
-
-							// Check for errors
-							glError = glGetError();
-							// If compilation failed
-							if(shaderCompileResult == 0)
-							{
-								// Assign an error
-								int shaderCompileLogLength = 0;
-								glGetShaderiv(shaderHandles[i], GL_INFO_LOG_LENGTH, &shaderCompileLogLength);
+							combinedNames.pop_back();
+							combinedNames.pop_back();
+						}
 
 
-								// Get the actual error message
-								std::vector<char> shaderCompileErrorMessage(shaderCompileLogLength);
-								glGetShaderInfoLog(shaderHandles[i], shaderCompileLogLength, NULL, &shaderCompileErrorMessage[0]);
+						// Log an error with the error handler
+						ErrHandlerLoc::get().log(ErrorCode::Shader_creation_failed,
+							ErrorSource::Source_ShaderLoader,
+							"\"" + combinedNames + "\":\n" + Utilities::toString(glError));
+					}
+					else
+					{
+						unsigned int numOfShaders = ShaderType::ShaderType_NumOfTypes;
 
 
-								// Convert vector of chars to a string
-								std::string errorMessageTemp;
-								for(int j = 0; shaderCompileErrorMessage[j]; j++)
-									errorMessageTemp += shaderCompileErrorMessage[j];
-
-								// Log an error with a shader info log
-								p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorCode = ErrorCode::Shader_compile_failed;
-								p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorSource = ErrorSource::Source_ShaderLoader;
-								p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorMessage = errorMessageTemp;
-
-								// Log an error with the error handler
-								ErrHandlerLoc::get().log(ErrorCode::Shader_compile_failed,
-														 ErrorSource::Source_ShaderLoader,
-														 "\"" + p_command.m_objectData.m_shaderData.m_names[i] + "\":\n" + errorMessageTemp);
-
-								// Reset the shader handle
-								shaderHandles[i] = 0;
-							}
-							else
+						// If a compute shader exists, then load ONLY the compute shader
+						if(!p_command.m_objectData.m_shaderData.m_source[ShaderType::ShaderType_Compute].empty())
+							numOfShaders = 1;
+
+						// Create individual shaders
+						for(unsigned int i = 0; i < numOfShaders; i++)
+						{
+							if(!p_command.m_objectData.m_shaderData.m_source[i].empty())
 							{
 							{
-								// Attach shader to the program handle
-								glAttachShader(p_command.m_handle, shaderHandles[i]);
+								// Create a shader handle
+								shaderHandles[i] = glCreateShader(shaderTypes[i]);
 
 
 								// Check for errors
 								// Check for errors
 								glError = glGetError();
 								glError = glGetError();
 								if(glError != GL_NO_ERROR)
 								if(glError != GL_NO_ERROR)
 								{
 								{
-									// Reset the shader handle
-									shaderHandles[i] = 0;
-
 									// Log an error with a shader info log
 									// Log an error with a shader info log
-									p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorCode = ErrorCode::Shader_attach_failed;
+									p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorCode = ErrorCode::Shader_creation_failed;
 									p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorSource = ErrorSource::Source_ShaderLoader;
 									p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorSource = ErrorSource::Source_ShaderLoader;
-									p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorMessage = Utilities::toString(glError);
 
 
 									// Log an error with the error handler
 									// Log an error with the error handler
-									ErrHandlerLoc::get().log(ErrorCode::Shader_compile_failed,
-															 ErrorSource::Source_ShaderLoader,
-															 "\"" + p_command.m_objectData.m_shaderData.m_names[i] + "\":\n" + Utilities::toString(glError));
+									ErrHandlerLoc::get().log(ErrorCode::Shader_creation_failed,
+										ErrorSource::Source_ShaderLoader,
+										"\"" + p_command.m_objectData.m_shaderData.m_names[i] + "\":\n" + Utilities::toString(glError));
+								}
+								else
+								{
+									// Pass shader source code and compile it
+									const char *shaderSource = p_command.m_objectData.m_shaderData.m_source[i].c_str();
+									glShaderSource(shaderHandles[i], 1, &shaderSource, NULL);
+									glCompileShader(shaderHandles[i]);
+
+									// Check for shader compilation errors
+									GLint shaderCompileResult = 0;
+									glGetShaderiv(shaderHandles[i], GL_COMPILE_STATUS, &shaderCompileResult);
+
+									// Check for errors
+									glError = glGetError();
+									// If compilation failed
+									if(shaderCompileResult == 0)
+									{
+										// Assign an error
+										int shaderCompileLogLength = 0;
+										glGetShaderiv(shaderHandles[i], GL_INFO_LOG_LENGTH, &shaderCompileLogLength);
+
+										// Get the actual error message
+										std::vector<char> shaderCompileErrorMessage(shaderCompileLogLength);
+										glGetShaderInfoLog(shaderHandles[i], shaderCompileLogLength, NULL, &shaderCompileErrorMessage[0]);
+
+										// Convert vector of chars to a string
+										std::string errorMessageTemp;
+										for(int j = 0; shaderCompileErrorMessage[j]; j++)
+											errorMessageTemp += shaderCompileErrorMessage[j];
+
+										// Log an error with a shader info log
+										p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorCode = ErrorCode::Shader_compile_failed;
+										p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorSource = ErrorSource::Source_ShaderLoader;
+										p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorMessage = errorMessageTemp;
+
+										// Log an error with the error handler
+										ErrHandlerLoc::get().log(ErrorCode::Shader_compile_failed,
+											ErrorSource::Source_ShaderLoader,
+											"\"" + p_command.m_objectData.m_shaderData.m_names[i] + "\":\n" + errorMessageTemp);
+
+										// Reset the shader handle
+										shaderHandles[i] = 0;
+									}
+									else
+									{
+										// Attach shader to the program handle
+										glAttachShader(p_command.m_handle, shaderHandles[i]);
+
+										// Check for errors
+										glError = glGetError();
+										if(glError != GL_NO_ERROR)
+										{
+											// Reset the shader handle
+											shaderHandles[i] = 0;
+
+											// Log an error with a shader info log
+											p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorCode = ErrorCode::Shader_attach_failed;
+											p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorSource = ErrorSource::Source_ShaderLoader;
+											p_command.m_objectData.m_shaderData.m_errorMessages[i].m_errorMessage = Utilities::toString(glError);
+
+											// Log an error with the error handler
+											ErrHandlerLoc::get().log(ErrorCode::Shader_compile_failed,
+												ErrorSource::Source_ShaderLoader,
+												"\"" + p_command.m_objectData.m_shaderData.m_names[i] + "\":\n" + Utilities::toString(glError));
+										}
+									}
 								}
 								}
 							}
 							}
 						}
 						}
-					}
-				}
 
 
-				GLint shaderLinkingResult;
-				glLinkProgram(p_command.m_handle);
+						GLint shaderLinkingResult;
+						glLinkProgram(p_command.m_handle);
 
 
-				// Check for linking errors. If an error has occurred, get the error message and throw an exception
-				glGetProgramiv(p_command.m_handle, GL_LINK_STATUS, &shaderLinkingResult);
+						// Check for linking errors. If an error has occurred, get the error message and throw an exception
+						glGetProgramiv(p_command.m_handle, GL_LINK_STATUS, &shaderLinkingResult);
 
 
-				// If shader loading was successful
-				if(shaderLinkingResult)
-				{
+						// If shader loading was successful
+						if(shaderLinkingResult)
+						{
 
 
-					glUseProgram(p_command.m_handle);
+							glUseProgram(p_command.m_handle);
 
 
-					// Generate uniform update list, after the shader has been compiled
-					p_command.m_objectData.m_shaderData.m_uniformUpdater.generateUpdateList();
+							// Generate uniform update list, after the shader has been compiled
+							p_command.m_objectData.m_shaderData.m_uniformUpdater.generateUpdateList();
 
 
-					// Update some of the uniforms (that do not change frame to frame)
-					//p_command.m_objectData.m_shaderData.m_uniformUpdater.generateUpdateList();
-					p_command.m_objectData.m_shaderData.m_uniformUpdater.updateTextureUniforms();
-					p_command.m_objectData.m_shaderData.m_uniformUpdater.updateBlockBindingPoints();
-					p_command.m_objectData.m_shaderData.m_uniformUpdater.updateSSBBindingPoints();
-				}
-				// If shader loading failed
-				else
-				{
-					// Reset shader handle
-					p_command.m_handle = 0;
+							// Update some of the uniforms (that do not change frame to frame)
+							//p_command.m_objectData.m_shaderData.m_uniformUpdater.generateUpdateList();
+							p_command.m_objectData.m_shaderData.m_uniformUpdater.updateTextureUniforms();
+							p_command.m_objectData.m_shaderData.m_uniformUpdater.updateBlockBindingPoints();
+							p_command.m_objectData.m_shaderData.m_uniformUpdater.updateSSBBindingPoints();
+						}
+						// If shader loading failed
+						else
+						{
+							// Reset shader handle
+							p_command.m_handle = 0;
 
 
-					GLsizei shaderLinkLogLength = 0;
-					std::string errorMessageTemp;
-					glGetShaderiv(p_command.m_handle, GL_INFO_LOG_LENGTH, &shaderLinkLogLength);
+							GLsizei shaderLinkLogLength = 0;
+							std::string errorMessageTemp;
+							glGetShaderiv(p_command.m_handle, GL_INFO_LOG_LENGTH, &shaderLinkLogLength);
 
 
-					// Sometimes OpenGL cannot retrieve the error string, so check that just in case
-					if(shaderLinkLogLength > 0)
-					{
-						// Get the actual error message
-						std::vector<char> shaderLinkErrorMessage(shaderLinkLogLength);
-						glGetShaderInfoLog(p_command.m_handle, shaderLinkLogLength, NULL, &shaderLinkErrorMessage[0]);
+							// Sometimes OpenGL cannot retrieve the error string, so check that just in case
+							if(shaderLinkLogLength > 0)
+							{
+								// Get the actual error message
+								std::vector<char> shaderLinkErrorMessage(shaderLinkLogLength);
+								glGetShaderInfoLog(p_command.m_handle, shaderLinkLogLength, NULL, &shaderLinkErrorMessage[0]);
 
 
-						// Convert vector of chars to a string
-						for(int i = 0; shaderLinkErrorMessage[i]; i++)
-							errorMessageTemp += shaderLinkErrorMessage[i];
-					}
-					else
-						errorMessageTemp = "Couldn't retrieve the error";
+								// Convert vector of chars to a string
+								for(int i = 0; shaderLinkErrorMessage[i]; i++)
+									errorMessageTemp += shaderLinkErrorMessage[i];
+							}
+							else
+								errorMessageTemp = "Couldn't retrieve the error";
 
 
-					// Log an error with a shader info log
-					p_command.m_objectData.m_shaderData.m_errorMessages[0].m_errorCode = ErrorCode::Shader_link_failed;
-					p_command.m_objectData.m_shaderData.m_errorMessages[0].m_errorSource = ErrorSource::Source_ShaderLoader;
-					p_command.m_objectData.m_shaderData.m_errorMessages[0].m_errorMessage = errorMessageTemp;
+							// Log an error with a shader info log
+							p_command.m_objectData.m_shaderData.m_errorMessages[0].m_errorCode = ErrorCode::Shader_link_failed;
+							p_command.m_objectData.m_shaderData.m_errorMessages[0].m_errorSource = ErrorSource::Source_ShaderLoader;
+							p_command.m_objectData.m_shaderData.m_errorMessages[0].m_errorMessage = errorMessageTemp;
 
 
-					// Log an error with the error handler
-					ErrHandlerLoc::get().log(ErrorCode::Shader_link_failed, ErrorSource::Source_ShaderLoader, errorMessageTemp);
+							// Log an error with the error handler
+							ErrHandlerLoc::get().log(ErrorCode::Shader_link_failed, ErrorSource::Source_ShaderLoader, errorMessageTemp);
+						}
+
+						// Iterate over all shaders and detach them
+						for(unsigned int i = 0; i < ShaderType_NumOfTypes; i++)
+						{
+							// If shader is valid
+							if(shaderHandles[i] != 0)
+							{
+								glDetachShader(p_command.m_handle, shaderHandles[i]);
+							}
+						}
+					}
 				}
 				}
+				break;
 
 
-				// Iterate over all shaders and detach them
-				for(unsigned int i = 0; i < ShaderType_NumOfTypes; i++)
+			case LoadObject_Texture2D:
 				{
 				{
-					// If shader is valid
-					if(shaderHandles[i] != 0)
+					// Generate, bind and upload the texture
+					glGenTextures(1, &p_command.m_handle);
+					glBindTexture(GL_TEXTURE_2D, p_command.m_handle);
+					glTexImage2D(GL_TEXTURE_2D,
+						p_command.m_objectData.m_tex2DData.m_mipmapLevel,
+						p_command.m_objectData.m_tex2DData.m_texDataFormat,
+						p_command.m_objectData.m_tex2DData.m_textureWidth,
+						p_command.m_objectData.m_tex2DData.m_textureHeight,
+						0,
+						p_command.m_objectData.m_tex2DData.m_texFormat,
+						p_command.m_objectData.m_tex2DData.m_texDataType,
+						p_command.m_objectData.m_tex2DData.m_data);
+
+					// Generate mipmaps if they are enabled, and set texture filtering to use mipmaps
+					if(p_command.m_objectData.m_tex2DData.m_enableMipmap)
+					{
+						glGenerateMipmap(GL_TEXTURE_2D);
+						// Texture filtering mode, when image is minimized
+						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, Config::textureVar().gl_texture_minification_mipmap);
+						// Texture filtering mode, when image is magnified
+						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, Config::textureVar().gl_texture_magnification_mipmap);
+					}
+					else
 					{
 					{
-						glDetachShader(p_command.m_handle, shaderHandles[i]);
+						// Texture filtering mode, when image is minimized
+						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, Config::textureVar().gl_texture_minification);
+						// Texture filtering mode, when image is magnified
+						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, Config::textureVar().gl_texture_magnification);
 					}
 					}
+
+					// Texture anisotropic filtering
+					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Config::textureVar().gl_texture_anisotropy);
 				}
 				}
-			}
-		}
-			break;
+				break;
 
 
-		case LoadObject_Texture2D:
-		{
-			// Generate, bind and upload the texture
-			glGenTextures(1, &p_command.m_handle);
-			glBindTexture(GL_TEXTURE_2D, p_command.m_handle);
-			glTexImage2D(GL_TEXTURE_2D,
-						 p_command.m_objectData.m_tex2DData.m_mipmapLevel,
-						 p_command.m_objectData.m_tex2DData.m_texDataFormat,
-						 p_command.m_objectData.m_tex2DData.m_textureWidth, 
-						 p_command.m_objectData.m_tex2DData.m_textureHeight,
-						 0, 
-						 p_command.m_objectData.m_tex2DData.m_texFormat,
-						 p_command.m_objectData.m_tex2DData.m_texDataType,
-						 p_command.m_objectData.m_tex2DData.m_data);
-
-			// Generate mipmaps if they are enabled, and set texture filtering to use mipmaps
-			if(p_command.m_objectData.m_tex2DData.m_enableMipmap)
-			{
-				glGenerateMipmap(GL_TEXTURE_2D);
-				// Texture filtering mode, when image is minimized
-				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, Config::textureVar().gl_texture_minification_mipmap);
-				// Texture filtering mode, when image is magnified
-				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, Config::textureVar().gl_texture_magnification_mipmap);
-			}
-			else
-			{
-				// Texture filtering mode, when image is minimized
-				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, Config::textureVar().gl_texture_minification);
-				// Texture filtering mode, when image is magnified
-				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, Config::textureVar().gl_texture_magnification);
-			}
+			case LoadObject_TextureCube:
+				{
+					// Generate, bind and upload the texture
+					glGenTextures(1, &p_command.m_handle);
+					glBindTexture(GL_TEXTURE_CUBE_MAP, p_command.m_handle);
 
 
-			// Texture anisotropic filtering
-			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Config::textureVar().gl_texture_anisotropy);
-		}
-			break;
+					for(unsigned int face = CubemapFace_PositiveX; face < CubemapFace_NumOfFaces; face++)
+					{
+						glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
+							p_command.m_objectData.m_cubeMapData.m_mipmapLevel,
+							p_command.m_objectData.m_cubeMapData.m_texFormat,
+							p_command.m_objectData.m_cubeMapData.m_textureWidth,
+							p_command.m_objectData.m_cubeMapData.m_textureHeight,
+							0,
+							p_command.m_objectData.m_cubeMapData.m_texFormat,
+							GL_UNSIGNED_BYTE,
+							p_command.m_objectData.m_cubeMapData.m_data[face]);
+
+						// Release memory
+						//FreeImage_Unload(m_bitmap[face]);
+						//m_pixelData[face] = nullptr;
+						//m_bitmap[face] = nullptr;
 
 
-		case LoadObject_TextureCube:
-		{
-			// Generate, bind and upload the texture
-			glGenTextures(1, &p_command.m_handle);
-			glBindTexture(GL_TEXTURE_CUBE_MAP, p_command.m_handle);
+					}
 
 
-			for (unsigned int face = CubemapFace_PositiveX; face < CubemapFace_NumOfFaces; face++)
-			{
-				glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
-					p_command.m_objectData.m_cubeMapData.m_mipmapLevel,
-					p_command.m_objectData.m_cubeMapData.m_texFormat,
-					p_command.m_objectData.m_cubeMapData.m_textureWidth,
-					p_command.m_objectData.m_cubeMapData.m_textureHeight,
-					0,
-					p_command.m_objectData.m_cubeMapData.m_texFormat,
-					GL_UNSIGNED_BYTE,
-					p_command.m_objectData.m_cubeMapData.m_data[face]);
-
-				// Release memory
-				//FreeImage_Unload(m_bitmap[face]);
-				//m_pixelData[face] = nullptr;
-				//m_bitmap[face] = nullptr;
+					glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
 
 
-			}
-			
-			glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
-
-			// Generate  mipmaps if they are enabled
-			if (Config::textureVar().generate_mipmaps)
-				glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
-
-			// Texture filtering mode, when image is minimized
-			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, Config::textureVar().gl_texture_minification);
-			// Texture filtering mode, when image is magnified
-			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, Config::textureVar().gl_texture_magnification);
-			// Texture anisotropic filtering
-			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, Config::textureVar().gl_texture_anisotropy);
-		}
-			break;
+					// Generate  mipmaps if they are enabled
+					if(Config::textureVar().generate_mipmaps)
+						glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
 
 
-		case LoadObject_Model:
-		{
-			// Create and bind the Vertex Array Object
-			glGenVertexArrays(1, &p_command.m_handle);
-			glBindVertexArray(p_command.m_handle);
-
-			// Create the m_buffers
-			glGenBuffers(sizeof(p_command.m_objectData.m_modelData.m_buffers) / sizeof(p_command.m_objectData.m_modelData.m_buffers[0]), p_command.m_objectData.m_modelData.m_buffers);
-
-			// Upload indices
-			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_command.m_objectData.m_modelData.m_buffers[ModelBuffer_Index]);
-			glBufferData(GL_ELEMENT_ARRAY_BUFFER, 
-						 p_command.m_objectData.m_modelData.m_size[ModelBuffer_Index], 
-						 p_command.m_objectData.m_modelData.m_data[ModelBuffer_Index], 
-						 GL_STATIC_DRAW);
-
-			// Loop over all the buffer types except index buffer 
-			// (since index buffer does not share the same properties as other buffer types)
-			for(unsigned int i = 0; i < ModelBuffer_NumTypesWithoutIndex; i++)
-			{
-				// Bind a buffer and upload data to it
-				glBindBuffer(GL_ARRAY_BUFFER, p_command.m_objectData.m_modelData.m_buffers[i]);
-				glBufferData(GL_ARRAY_BUFFER,
-							 p_command.m_objectData.m_modelData.m_size[i],
-							 p_command.m_objectData.m_modelData.m_data[i],
-							 GL_STATIC_DRAW);
-
-				// Enable and bind the buffer to a vertex attribute array (so it can be accessed in the shader)
-				glEnableVertexAttribArray(i);
-				glVertexAttribPointer(i, p_command.m_objectData.m_modelData.m_numElements[i], GL_FLOAT, GL_FALSE, 0, 0);
-			}
-		}
-			break;
+					// Texture filtering mode, when image is minimized
+					glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, Config::textureVar().gl_texture_minification);
+					// Texture filtering mode, when image is magnified
+					glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, Config::textureVar().gl_texture_magnification);
+					// Texture anisotropic filtering
+					glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, Config::textureVar().gl_texture_anisotropy);
+				}
+				break;
 
 
-		default:
-			break;
+			case LoadObject_Model:
+				{
+					// Create and bind the Vertex Array Object
+					glGenVertexArrays(1, &p_command.m_handle);
+					glBindVertexArray(p_command.m_handle);
+
+					// Create the m_buffers
+					glGenBuffers(sizeof(p_command.m_objectData.m_modelData.m_buffers) / sizeof(p_command.m_objectData.m_modelData.m_buffers[0]), p_command.m_objectData.m_modelData.m_buffers);
+
+					// Upload indices
+					glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_command.m_objectData.m_modelData.m_buffers[ModelBuffer_Index]);
+					glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+						p_command.m_objectData.m_modelData.m_size[ModelBuffer_Index],
+						p_command.m_objectData.m_modelData.m_data[ModelBuffer_Index],
+						GL_STATIC_DRAW);
+
+					// Loop over all the buffer types except index buffer 
+					// (since index buffer does not share the same properties as other buffer types)
+					for(unsigned int i = 0; i < ModelBuffer_NumTypesWithoutIndex; i++)
+					{
+						// Bind a buffer and upload data to it
+						glBindBuffer(GL_ARRAY_BUFFER, p_command.m_objectData.m_modelData.m_buffers[i]);
+						glBufferData(GL_ARRAY_BUFFER,
+							p_command.m_objectData.m_modelData.m_size[i],
+							p_command.m_objectData.m_modelData.m_data[i],
+							GL_STATIC_DRAW);
+
+						// Enable and bind the buffer to a vertex attribute array (so it can be accessed in the shader)
+						glEnableVertexAttribArray(i);
+						glVertexAttribPointer(i, p_command.m_objectData.m_modelData.m_numElements[i], GL_FLOAT, GL_FALSE, 0, 0);
+					}
+				}
+				break;
+
+			default:
+				break;
 		}
 		}
 	}
 	}
 	inline void processCommand(const UnloadObjectType p_objectType, const int p_count, unsigned int *p_handles)
 	inline void processCommand(const UnloadObjectType p_objectType, const int p_count, unsigned int *p_handles)

+ 3 - 1
Praxis3D/Source/RendererFrontend.cpp

@@ -155,6 +155,8 @@ ErrorCode RendererFrontend::init()
 	return returnCode;
 	return returnCode;
 }
 }
 
 
+const bool RendererFrontend::getRenderFinalToTexture() const { return (m_renderPassData != nullptr) ? m_renderPassData->getRenderFinalToTexture() : false; }
+
 void RendererFrontend::setGUIPassFunctorSequence(FunctorSequence *p_GUIPassFunctorSequence)
 void RendererFrontend::setGUIPassFunctorSequence(FunctorSequence *p_GUIPassFunctorSequence)
 {
 {
 	m_renderPassData->setGUIPassFunctorSequence(p_GUIPassFunctorSequence);
 	m_renderPassData->setGUIPassFunctorSequence(p_GUIPassFunctorSequence);
@@ -380,7 +382,7 @@ void RendererFrontend::renderFrame(SceneObjects &p_sceneObjects, const float p_d
 	unsigned int numLoadedObjectsThisFrame = 0;
 	unsigned int numLoadedObjectsThisFrame = 0;
 	const unsigned int maxLoadedObjectsThisFrame = Config::rendererVar().objects_loaded_per_frame;
 	const unsigned int maxLoadedObjectsThisFrame = Config::rendererVar().objects_loaded_per_frame;
 
 
-	// Iterate over all objects to be rendered with geometry shader
+	// Iterate over all objects that require to be loaded to video memory
 	for(auto entity : p_sceneObjects.m_objectsToLoadToVideoMemory)
 	for(auto entity : p_sceneObjects.m_objectsToLoadToVideoMemory)
 	{
 	{
 		GraphicsLoadToVideoMemoryComponent &component = p_sceneObjects.m_objectsToLoadToVideoMemory.get<GraphicsLoadToVideoMemoryComponent>(entity);
 		GraphicsLoadToVideoMemoryComponent &component = p_sceneObjects.m_objectsToLoadToVideoMemory.get<GraphicsLoadToVideoMemoryComponent>(entity);

+ 4 - 1
Praxis3D/Source/RendererFrontend.h

@@ -56,6 +56,8 @@ public:
 
 
 	ErrorCode init();
 	ErrorCode init();
 
 
+	const bool getRenderFinalToTexture() const;
+
 	void setGUIPassFunctorSequence(FunctorSequence *p_GUIPassFunctorSequence);
 	void setGUIPassFunctorSequence(FunctorSequence *p_GUIPassFunctorSequence);
 	void setRenderFinalToTexture(const bool p_renderToTexture);
 	void setRenderFinalToTexture(const bool p_renderToTexture);
 	void setRenderToTextureResolution(const glm::ivec2 p_renderToTextureResolution);
 	void setRenderToTextureResolution(const glm::ivec2 p_renderToTextureResolution);
@@ -109,7 +111,8 @@ protected:
 						p_modelData.m_meshes[meshIndex].m_materials[MaterialType::MaterialType_Diffuse].m_texture.getHandle(),
 						p_modelData.m_meshes[meshIndex].m_materials[MaterialType::MaterialType_Diffuse].m_texture.getHandle(),
 						p_modelData.m_meshes[meshIndex].m_materials[MaterialType::MaterialType_Normal].m_texture.getHandle(),
 						p_modelData.m_meshes[meshIndex].m_materials[MaterialType::MaterialType_Normal].m_texture.getHandle(),
 						p_modelData.m_meshes[meshIndex].m_materials[MaterialType::MaterialType_Emissive].m_texture.getHandle(),
 						p_modelData.m_meshes[meshIndex].m_materials[MaterialType::MaterialType_Emissive].m_texture.getHandle(),
-						p_modelData.m_meshes[meshIndex].m_materials[MaterialType::MaterialType_Combined].m_texture.getHandle())
+						p_modelData.m_meshes[meshIndex].m_materials[MaterialType::MaterialType_Combined].m_texture.getHandle(),
+						p_modelData.m_meshes[meshIndex].m_textureWrapMode)
 				);
 				);
 			}
 			}
 		}
 		}

+ 42 - 1
Praxis3D/Source/RendererScene.cpp

@@ -10,7 +10,8 @@
 RendererScene::RendererScene(RendererSystem *p_system, SceneLoader *p_sceneLoader) : SystemScene(p_system, p_sceneLoader, Properties::PropertyID::Renderer)
 RendererScene::RendererScene(RendererSystem *p_system, SceneLoader *p_sceneLoader) : SystemScene(p_system, p_sceneLoader, Properties::PropertyID::Renderer)
 {
 {
 	m_renderTask = new RenderTask(this, p_system->getRenderer());
 	m_renderTask = new RenderTask(this, p_system->getRenderer());
-	m_renderToTexture = false; 
+	m_renderToTexture = false;
+	m_firstLoadingDone = false;
 	m_renderToTextureResolution = glm::ivec2(Config::graphicsVar().current_resolution_x, Config::graphicsVar().current_resolution_y);
 	m_renderToTextureResolution = glm::ivec2(Config::graphicsVar().current_resolution_x, Config::graphicsVar().current_resolution_y);
 }
 }
 
 
@@ -186,6 +187,8 @@ void RendererScene::update(const float p_deltaTime)
 	// Get the entity registry 
 	// Get the entity registry 
 	auto &entityRegistry = worldScene->getEntityRegistry();
 	auto &entityRegistry = worldScene->getEntityRegistry();
 
 
+	bool loadingStatus = false;
+
 	//	 _______________________________
 	//	 _______________________________
 	//	|							    |
 	//	|							    |
 	//	|	 CHECK FOR LOAD PENDING		|
 	//	|	 CHECK FOR LOAD PENDING		|
@@ -217,6 +220,9 @@ void RendererScene::update(const float p_deltaTime)
 					// Add load to memory component, to mark the entity as loading
 					// Add load to memory component, to mark the entity as loading
 					worldScene->addComponent<GraphicsLoadToMemoryComponent>(entity, entity);
 					worldScene->addComponent<GraphicsLoadToMemoryComponent>(entity, entity);
 
 
+					// Set the loading status flag to true
+					loadingStatus = true;
+
 					// Reset load pending flag
 					// Reset load pending flag
 					component.resetLoadPending();
 					component.resetLoadPending();
 					component.setLoadedToMemory(false);
 					component.setLoadedToMemory(false);
@@ -231,6 +237,9 @@ void RendererScene::update(const float p_deltaTime)
 				// Add load to memory component, to mark the entity as loading
 				// Add load to memory component, to mark the entity as loading
 				worldScene->addComponent<GraphicsLoadToMemoryComponent>(entity, entity);
 				worldScene->addComponent<GraphicsLoadToMemoryComponent>(entity, entity);
 
 
+				// Set the loading status flag to true
+				loadingStatus = true;
+
 				// Reset load pending flag
 				// Reset load pending flag
 				component.resetLoadPending();
 				component.resetLoadPending();
 				component.setLoadedToMemory(false);
 				component.setLoadedToMemory(false);
@@ -268,6 +277,11 @@ void RendererScene::update(const float p_deltaTime)
 			// Remove load-to-video-memory component to mark the entity as loaded to GPU
 			// Remove load-to-video-memory component to mark the entity as loaded to GPU
 			worldScene->removeComponent<GraphicsLoadToVideoMemoryComponent>(entity);
 			worldScene->removeComponent<GraphicsLoadToVideoMemoryComponent>(entity);
 		}
 		}
+		else
+		{
+			// Set the loading status flag to true
+			loadingStatus = true;
+		}
 	}
 	}
 
 
 	//	 _______________________________
 	//	 _______________________________
@@ -316,6 +330,11 @@ void RendererScene::update(const float p_deltaTime)
 							loadToVideoMemoryComponent.m_objectsToLoad.emplace(loadableObjectsFromModel[i]);
 							loadToVideoMemoryComponent.m_objectsToLoad.emplace(loadableObjectsFromModel[i]);
 				}
 				}
 			}
 			}
+			else
+			{
+				// Set the loading status flag to true
+				loadingStatus = true;
+			}
 		}
 		}
 		else
 		else
 		{
 		{
@@ -371,6 +390,28 @@ void RendererScene::update(const float p_deltaTime)
 					}
 					}
 				}
 				}
 			}
 			}
+			else
+			{
+				// Set the loading status flag to true
+				loadingStatus = true;
+			}
+		}
+	}
+
+	m_loadingStatus = loadingStatus;
+
+	// If the render-to-texture is not set (i.e. no scene editor), do not process drawing while there are objects being loaded
+	// Perform only during the first initial scene loading
+	if(!m_firstLoadingDone && !m_renderTask->m_renderer.getRenderFinalToTexture())
+	{
+		if(m_loadingStatus)
+		{
+			m_sceneObjects.m_processDrawing = false;
+		}
+		else
+		{
+			m_sceneObjects.m_processDrawing = true;
+			m_firstLoadingDone = true;
 		}
 		}
 	}
 	}
 
 

+ 7 - 9
Praxis3D/Source/RendererScene.h

@@ -84,22 +84,14 @@ struct LoadableComponentContainer
 // Used to store processed objects, so they can be sent to the renderer
 // Used to store processed objects, so they can be sent to the renderer
 struct SceneObjects
 struct SceneObjects
 {
 {
-	SceneObjects() { }
+	SceneObjects() : m_processDrawing(true) { }
 
 
 	// ECS registry views
 	// ECS registry views
-	//ModelSpatialView m_models;
-
 	decltype(std::declval<entt::basic_registry<EntityID>>().view<ModelComponent, SpatialComponent>(entt::exclude<ShaderComponent, GraphicsLoadToMemoryComponent, GraphicsLoadToVideoMemoryComponent>)) m_models;
 	decltype(std::declval<entt::basic_registry<EntityID>>().view<ModelComponent, SpatialComponent>(entt::exclude<ShaderComponent, GraphicsLoadToMemoryComponent, GraphicsLoadToVideoMemoryComponent>)) m_models;
 	decltype(std::declval<entt::basic_registry<EntityID>>().view<ModelComponent, ShaderComponent, SpatialComponent>(entt::exclude<GraphicsLoadToMemoryComponent, GraphicsLoadToVideoMemoryComponent>)) m_modelsWithShaders;
 	decltype(std::declval<entt::basic_registry<EntityID>>().view<ModelComponent, ShaderComponent, SpatialComponent>(entt::exclude<GraphicsLoadToMemoryComponent, GraphicsLoadToVideoMemoryComponent>)) m_modelsWithShaders;
 	decltype(std::declval<entt::basic_registry<EntityID>>().view<GraphicsLoadToVideoMemoryComponent>(entt::exclude<GraphicsLoadToMemoryComponent>)) m_objectsToLoadToVideoMemory;
 	decltype(std::declval<entt::basic_registry<EntityID>>().view<GraphicsLoadToVideoMemoryComponent>(entt::exclude<GraphicsLoadToMemoryComponent>)) m_objectsToLoadToVideoMemory;
 	decltype(std::declval<entt::basic_registry<EntityID>>().view<LightComponent, SpatialComponent>(entt::exclude<>)) m_lights;
 	decltype(std::declval<entt::basic_registry<EntityID>>().view<LightComponent, SpatialComponent>(entt::exclude<>)) m_lights;
 
 
-
-	//decltype(entt::basic_registry<EntityID>::view<ModelComponent, SpatialComponent>(entt::exclude<ShaderComponent, GraphicsLoadToMemoryComponent, GraphicsLoadToVideoMemoryComponent>)) m_models;
-	//ModelShaderSpatialView m_modelsWithShaders;
-	//LightSpatialView m_lights;
-	//LoadToVideoMemoryView m_objectsToLoadToVideoMemory;
-
 	// Camera
 	// Camera
 	glm::mat4 m_cameraViewMatrix;
 	glm::mat4 m_cameraViewMatrix;
 
 
@@ -108,6 +100,9 @@ struct SceneObjects
 
 
 	// Objects that need to be removed from VRAM
 	// Objects that need to be removed from VRAM
 	std::vector<LoadableObjectsContainer> m_unloadFromVideoMemory;
 	std::vector<LoadableObjectsContainer> m_unloadFromVideoMemory;
+
+	// Should the scene be drawn (otherwise only load commands are processed)
+	bool m_processDrawing;
 };
 };
 
 
 class RendererScene : public SystemScene
 class RendererScene : public SystemScene
@@ -297,4 +292,7 @@ private:
 	// Render-to-texture data
 	// Render-to-texture data
 	bool m_renderToTexture;
 	bool m_renderToTexture;
 	glm::ivec2 m_renderToTextureResolution;
 	glm::ivec2 m_renderToTextureResolution;
+
+	// A flag tracking whether the first scene loading is done (to not trigger actions during loading after the scene was created)
+	bool m_firstLoadingDone;
 };
 };

+ 47 - 0
Praxis3D/Source/SceneLoader.cpp

@@ -84,12 +84,16 @@ ErrorCode SceneLoader::loadFromProperties(const PropertySet &p_sceneProperties)
 	// Check if the scene should be loaded in background
 	// Check if the scene should be loaded in background
 	if(p_sceneProperties.getPropertyByID(Properties::LoadInBackground).getBool())
 	if(p_sceneProperties.getPropertyByID(Properties::LoadInBackground).getBool())
 	{
 	{
+		m_loadInBackground = true;
+
 		// Start loading in background threads in all scenes
 		// Start loading in background threads in all scenes
 		for(int i = 0; i < Systems::NumberOfSystems; i++)
 		for(int i = 0; i < Systems::NumberOfSystems; i++)
 			m_systemScenes[i]->loadInBackground();
 			m_systemScenes[i]->loadInBackground();
 	}
 	}
 	else
 	else
 	{
 	{
+		m_loadInBackground = false;
+
 		// Preload all scenes sequentially
 		// Preload all scenes sequentially
 		for(int i = 0; i < Systems::NumberOfSystems; i++)
 		for(int i = 0; i < Systems::NumberOfSystems; i++)
 			m_systemScenes[i]->preload();
 			m_systemScenes[i]->preload();
@@ -665,6 +669,30 @@ void SceneLoader::importFromProperties(GraphicsComponentsConstructionInfo &p_con
 											if(auto heightScaleProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::HeightScale); heightScaleProperty)
 											if(auto heightScaleProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::HeightScale); heightScaleProperty)
 												newModelEntry.m_heightScale[meshDataIndex] = heightScaleProperty.getFloat();
 												newModelEntry.m_heightScale[meshDataIndex] = heightScaleProperty.getFloat();
 
 
+											// Get material wrap mode
+											if(auto wrapModeProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::WrapMode); wrapModeProperty)
+											{
+												switch(wrapModeProperty.getID())
+												{
+													case Properties::ClampToBorder:
+														newModelEntry.m_textureWrapMode[meshDataIndex] = TextureWrapType::TextureWrapType_ClampToBorder;
+														break;
+													case Properties::ClampToEdge:
+														newModelEntry.m_textureWrapMode[meshDataIndex] = TextureWrapType::TextureWrapType_ClampToEdge;
+														break;
+													case Properties::MirroredClampToEdge:
+														newModelEntry.m_textureWrapMode[meshDataIndex] = TextureWrapType::TextureWrapType_MirroredClampToEdge;
+														break;
+													case Properties::MirroredRepeat:
+														newModelEntry.m_textureWrapMode[meshDataIndex] = TextureWrapType::TextureWrapType_MirroredRepeat;
+														break;
+													case Properties::Repeat:
+													default:
+														newModelEntry.m_textureWrapMode[meshDataIndex] = TextureWrapType::TextureWrapType_Repeat;
+														break;
+												}
+											}
+
 											// Get material properties
 											// Get material properties
 											auto materialsProperty = meshesProperty.getPropertySet(iMesh).getPropertySetByID(Properties::Materials);
 											auto materialsProperty = meshesProperty.getPropertySet(iMesh).getPropertySetByID(Properties::Materials);
 
 
@@ -1170,6 +1198,25 @@ void SceneLoader::exportToProperties(const GraphicsComponentsConstructionInfo &p
 							meshPropertyArrayEntry.addProperty(Properties::PropertyID::EmissiveIntensity, model.m_emissiveIntensity[i]);
 							meshPropertyArrayEntry.addProperty(Properties::PropertyID::EmissiveIntensity, model.m_emissiveIntensity[i]);
 							meshPropertyArrayEntry.addProperty(Properties::PropertyID::HeightScale, model.m_heightScale[i]);
 							meshPropertyArrayEntry.addProperty(Properties::PropertyID::HeightScale, model.m_heightScale[i]);
 
 
+							switch(model.m_textureWrapMode[i])
+							{
+								case TextureWrapType::TextureWrapType_ClampToBorder:
+									meshPropertyArrayEntry.addProperty(Properties::PropertyID::WrapMode, Properties::PropertyID::ClampToBorder);
+									break;
+								case TextureWrapType::TextureWrapType_ClampToEdge:
+									meshPropertyArrayEntry.addProperty(Properties::PropertyID::WrapMode, Properties::PropertyID::ClampToEdge);
+									break;
+								case TextureWrapType::TextureWrapType_MirroredClampToEdge:
+									meshPropertyArrayEntry.addProperty(Properties::PropertyID::WrapMode, Properties::PropertyID::MirroredClampToEdge);
+									break;
+								case TextureWrapType::TextureWrapType_MirroredRepeat:
+									meshPropertyArrayEntry.addProperty(Properties::PropertyID::WrapMode, Properties::PropertyID::MirroredRepeat);
+									break;
+								case TextureWrapType::TextureWrapType_Repeat:
+									meshPropertyArrayEntry.addProperty(Properties::PropertyID::WrapMode, Properties::PropertyID::Repeat);
+									break;
+							}
+
 							auto &materialsPropertySet = meshPropertyArrayEntry.addPropertySet(Properties::Materials);
 							auto &materialsPropertySet = meshPropertyArrayEntry.addPropertySet(Properties::Materials);
 
 
 							// Go over each material
 							// Go over each material

+ 7 - 7
Praxis3D/Source/SolarTimeScript.h

@@ -335,7 +335,7 @@ public:
 			dX = cos(dEclipticLongitude);
 			dX = cos(dEclipticLongitude);
 			dRightAscension = atan2(dY, dX);
 			dRightAscension = atan2(dY, dX);
 			if(dRightAscension < 0.0) 
 			if(dRightAscension < 0.0) 
-				dRightAscension = dRightAscension + TWOPI;
+				dRightAscension = dRightAscension + Math::PI_2_DOUBLE;
 			dDeclination = asin(sin(dEclipticObliquity) * dSin_EclipticLongitude);
 			dDeclination = asin(sin(dEclipticObliquity) * dSin_EclipticLongitude);
 		}
 		}
 
 
@@ -350,9 +350,9 @@ public:
 			double dCos_HourAngle;
 			double dCos_HourAngle;
 			double dParallax;
 			double dParallax;
 			dGreenwichMeanSiderealTime = 6.6974243242 + 0.0657098283 * dElapsedJulianDays + dDecimalHours;
 			dGreenwichMeanSiderealTime = 6.6974243242 + 0.0657098283 * dElapsedJulianDays + dDecimalHours;
-			dLocalMeanSiderealTime = (dGreenwichMeanSiderealTime * 15 + m_longitude) * RAD;
+			dLocalMeanSiderealTime = (dGreenwichMeanSiderealTime * 15 + m_longitude) * Math::RAD_DOUBLE;
 			dHourAngle = dLocalMeanSiderealTime - dRightAscension;
 			dHourAngle = dLocalMeanSiderealTime - dRightAscension;
-			dLatitudeInRadians = m_latitude * RAD;
+			dLatitudeInRadians = m_latitude * Math::RAD_DOUBLE;
 			dCos_Latitude = cos(dLatitudeInRadians);
 			dCos_Latitude = cos(dLatitudeInRadians);
 			dSin_Latitude = sin(dLatitudeInRadians);
 			dSin_Latitude = sin(dLatitudeInRadians);
 			dCos_HourAngle = cos(dHourAngle);
 			dCos_HourAngle = cos(dHourAngle);
@@ -361,12 +361,12 @@ public:
 			dX = tan(dDeclination)*dCos_Latitude - dSin_Latitude * dCos_HourAngle;
 			dX = tan(dDeclination)*dCos_Latitude - dSin_Latitude * dCos_HourAngle;
 			m_azimuthAngle = atan2(dY, dX);
 			m_azimuthAngle = atan2(dY, dX);
 			if(m_azimuthAngle < 0.0)
 			if(m_azimuthAngle < 0.0)
-				m_azimuthAngle = m_azimuthAngle + TWOPI;
-			m_azimuthAngle = m_azimuthAngle / RAD;
+				m_azimuthAngle = m_azimuthAngle + Math::PI_2_DOUBLE;
+			m_azimuthAngle = m_azimuthAngle / Math::RAD_DOUBLE;
 
 
 			// Parallax Correction
 			// Parallax Correction
 			dParallax = (dEarthMeanRadius / dAstronomicalUnit) * sin(m_zenithAngle);
 			dParallax = (dEarthMeanRadius / dAstronomicalUnit) * sin(m_zenithAngle);
-			m_zenithAngle = (m_zenithAngle + dParallax) / RAD;
+			m_zenithAngle = (m_zenithAngle + dParallax) / Math::RAD_DOUBLE;
 			m_elevationAngle = 90.0f - m_zenithAngle;
 			m_elevationAngle = 90.0f - m_zenithAngle;
 		}
 		}
 	}
 	}
@@ -374,7 +374,7 @@ public:
 	void calcSunPositionOld()
 	void calcSunPositionOld()
 	{
 	{
 		// Get the fractional year in radians
 		// Get the fractional year in radians
-		float fracYear = (2.0f * (float)PI / 365.0f) * (m_dayOfYear - 1 + (m_hours - 12.0f) / 24.0f);
+		float fracYear = (2.0f * Math::PI / 365.0f) * (m_dayOfYear - 1 + (m_hours - 12.0f) / 24.0f);
 
 
 		// Get the equation of time in minutes
 		// Get the equation of time in minutes
 		float equatOfTime = 229.18f * (0.000075f + 0.001868f * cosf(fracYear) - 
 		float equatOfTime = 229.18f * (0.000075f + 0.001868f * cosf(fracYear) - 

+ 1 - 1
Praxis3D/Source/SpatialComponent.h

@@ -78,7 +78,7 @@ public:
 		}
 		}
 
 
 		// Process the spatial changes and record the world-space changes
 		// Process the spatial changes and record the world-space changes
-		/*newChanges = */m_spatialData.changeOccurred(*p_subject, p_changeType & Systems::Changes::Spatial::All);
+		newChanges = m_spatialData.changeOccurred(*p_subject, p_changeType & Systems::Changes::Spatial::All);
 
 
 		// Update spatial data
 		// Update spatial data
 		m_spatialData.update();
 		m_spatialData.update();

+ 15 - 9
Praxis3D/Source/SpatialDataManager.h

@@ -140,7 +140,7 @@ public:
 			incrementUpdateCount();
 			incrementUpdateCount();
 
 
 			if(!m_trackLocalChanges)
 			if(!m_trackLocalChanges)
-				m_changes |= Systems::Changes::Spatial::WorldTransform;
+				m_changes |= Systems::Changes::Spatial::WorldTransformNoScale;
 		}
 		}
 	}
 	}
 
 
@@ -156,7 +156,7 @@ public:
 		{
 		{
 			// Update both the local-space and parent world-space data
 			// Update both the local-space and parent world-space data
 			setLocalSpatialTransformData(p_subject.getSpatialTransformData(&m_parent, Systems::Changes::Spatial::AllLocal));
 			setLocalSpatialTransformData(p_subject.getSpatialTransformData(&m_parent, Systems::Changes::Spatial::AllLocal));
-			setWorldTransform(p_subject.getMat4(&m_parent, Systems::Changes::Spatial::WorldTransform));
+			setWorldTransform(p_subject.getMat4(&m_parent, Systems::Changes::Spatial::WorldTransformNoScale));
 			localOrWorldTransformChanged = true;
 			localOrWorldTransformChanged = true;
 		}
 		}
 		else // If the "All" flag wasn't set, check each local and world (parent) change
 		else // If the "All" flag wasn't set, check each local and world (parent) change
@@ -210,14 +210,20 @@ public:
 			// Check if everything in world-space of the parent object has changed
 			// Check if everything in world-space of the parent object has changed
 			if(CheckBitmask(p_changeType, Systems::Changes::Spatial::AllWorld))
 			if(CheckBitmask(p_changeType, Systems::Changes::Spatial::AllWorld))
 			{
 			{
-				setWorldTransform(p_subject.getMat4(&m_parent, Systems::Changes::Spatial::WorldTransform));
+				setWorldTransform(p_subject.getMat4(&m_parent, Systems::Changes::Spatial::WorldTransformNoScale));
 				localOrWorldTransformChanged = true;
 				localOrWorldTransformChanged = true;
 			}
 			}
 			else
 			else
 			{
 			{
-				if(CheckBitmask(p_changeType, Systems::Changes::Spatial::WorldTransform))
+				//if(CheckBitmask(p_changeType, Systems::Changes::Spatial::WorldTransform))
+				//{
+				//	setParentTransform(p_subject.getMat4(&m_parent, Systems::Changes::Spatial::WorldTransform));
+				//	localOrWorldTransformChanged = true;
+				//}
+
+				if(CheckBitmask(p_changeType, Systems::Changes::Spatial::WorldTransformNoScale))
 				{
 				{
-					setParentTransform(p_subject.getMat4(&m_parent, Systems::Changes::Spatial::WorldTransform));
+					setParentTransform(p_subject.getMat4(&m_parent, Systems::Changes::Spatial::WorldTransformNoScale));
 					localOrWorldTransformChanged = true;
 					localOrWorldTransformChanged = true;
 				}
 				}
 			}
 			}
@@ -229,13 +235,13 @@ public:
 		// Update the world-space data if any changes have been made
 		// Update the world-space data if any changes have been made
 		if(localOrWorldTransformChanged)
 		if(localOrWorldTransformChanged)
 		{
 		{
-			m_worldTransformNoScale = m_localSpace.m_transformMatNoScale * m_parentTransform;
+			m_worldTransformNoScale = m_parentTransform * m_localSpace.m_transformMatNoScale;
 			m_worldTransformWithScale = glm::scale(m_worldTransformNoScale, m_localSpace.m_spatialData.m_scale);
 			m_worldTransformWithScale = glm::scale(m_worldTransformNoScale, m_localSpace.m_spatialData.m_scale);
 
 
 			m_worldTransformUpToDate = true;
 			m_worldTransformUpToDate = true;
 
 
 			incrementUpdateCount();
 			incrementUpdateCount();
-			m_changes |= Systems::Changes::Spatial::WorldTransform;
+			m_changes |= Systems::Changes::Spatial::WorldTransformNoScale;
 		}
 		}
 
 
 		return m_changes;
 		return m_changes;
@@ -415,8 +421,8 @@ public:
 		m_localScaleUpToDate = true;
 		m_localScaleUpToDate = true;
 
 
 		// Variables that became outdated
 		// Variables that became outdated
-		m_localEverythingUpToDate = false;
-		m_localTransformUpToDate = false;
+		//m_localEverythingUpToDate = false;
+		//m_localTransformUpToDate = false;
 		m_worldTransformUpToDate = false;
 		m_worldTransformUpToDate = false;
 
 
 		if(m_trackLocalChanges)
 		if(m_trackLocalChanges)

+ 5 - 1
Praxis3D/Source/System.h

@@ -61,7 +61,7 @@ class SystemScene : public ObservedSubject, public Observer
 	friend SystemBase;
 	friend SystemBase;
 public:
 public:
 	SystemScene(SystemBase *p_system, SceneLoader *p_sceneLoader, Properties::PropertyID p_objectType)
 	SystemScene(SystemBase *p_system, SceneLoader *p_sceneLoader, Properties::PropertyID p_objectType)
-		: Observer(p_objectType), m_initialized(false), m_system(p_system), m_sceneLoader(p_sceneLoader) { }
+		: Observer(p_objectType), m_initialized(false), m_loadingStatus(false), m_system(p_system), m_sceneLoader(p_sceneLoader) { }
 	//~SystemScene();
 	//~SystemScene();
 
 
 	// Gets the parent system
 	// Gets the parent system
@@ -130,8 +130,12 @@ public:
 	// Return the parent scene loader
 	// Return the parent scene loader
 	inline SceneLoader *getSceneLoader() { return m_sceneLoader; }
 	inline SceneLoader *getSceneLoader() { return m_sceneLoader; }
 
 
+	// Returns true if the scene is currently performing any loading
+	inline bool getLoadingStatus() const { return m_loadingStatus; }
+
 protected:
 protected:
 	bool m_initialized;
 	bool m_initialized;
+	bool m_loadingStatus;
 	SystemBase *m_system;
 	SystemBase *m_system;
 	SceneLoader *m_sceneLoader;
 	SceneLoader *m_sceneLoader;
 };
 };

+ 13 - 0
Praxis3D/Source/Version.h

@@ -0,0 +1,13 @@
+#pragma once
+
+#define VERSION_TO_STRING_HELPER(v) #v
+#define VERSION_TO_STRING(v) VERSION_TO_STRING_HELPER(v)
+
+#define PRAXIS3D_COMMIT_DATE 2023-12-13
+
+#define PRAXIS3D_VERSION_MAJOR 0
+#define PRAXIS3D_VERSION_MINOR 1
+#define PRAXIS3D_VERSION_PATCH 1
+
+#define PRAXIS3D_VERSION ((PRAXIS3D_VERSION_MAJOR << 16) | (PRAXIS3D_VERSION_MINOR << 8) | PRAXIS3D_VERSION_PATCH)
+#define PRAXIS3D_VERSION_STRING VERSION_TO_STRING(PRAXIS3D_VERSION_MAJOR) "." VERSION_TO_STRING(PRAXIS3D_VERSION_MINOR) "." VERSION_TO_STRING(PRAXIS3D_VERSION_PATCH)

+ 5 - 1
Praxis3D/Source/Window.cpp

@@ -1,6 +1,7 @@
 
 
 #include "ErrorHandlerLocator.h"
 #include "ErrorHandlerLocator.h"
 #include "GUIHandler.h"
 #include "GUIHandler.h"
+#include "Version.h"
 #include "Window.h"
 #include "Window.h"
 
 
 Window::Window()
 Window::Window()
@@ -41,6 +42,9 @@ ErrorCode Window::init()
 {
 {
 	ErrorCode returnError = ErrorCode::Success;
 	ErrorCode returnError = ErrorCode::Success;
 
 
+	// Add the current version number to the window name
+	Config::m_windowVar.name = Config::m_windowVar.name + " v" + PRAXIS3D_VERSION_STRING;
+
 	if(SDL_Init(SDL_INIT_VIDEO) < 0)
 	if(SDL_Init(SDL_INIT_VIDEO) < 0)
 		returnError = ErrorCode::SDL_video_init_failed;
 		returnError = ErrorCode::SDL_video_init_failed;
 	else
 	else
@@ -116,7 +120,7 @@ ErrorCode Window::createWindow()
 	unsigned int flags = Config::windowVar().resizable ? flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE : SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
 	unsigned int flags = Config::windowVar().resizable ? flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE : SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
 
 
 	// Spawn a window
 	// Spawn a window
-	m_SDLWindow = SDL_CreateWindow(Config::windowVar().name.c_str(), 
+	m_SDLWindow = SDL_CreateWindow(Config::windowVar().name.c_str(),
 								   Config::windowVar().window_position_x, Config::windowVar().window_position_y,
 								   Config::windowVar().window_position_x, Config::windowVar().window_position_y,
 								   Config::graphicsVar().current_resolution_x, Config::graphicsVar().current_resolution_y, flags);
 								   Config::graphicsVar().current_resolution_x, Config::graphicsVar().current_resolution_y, flags);
 
 

+ 43 - 2
Praxis3D/Source/WorldScene.cpp

@@ -101,7 +101,7 @@ ErrorCode WorldScene::addComponents(const EntityID p_entityID, const ComponentsC
 			}
 			}
 
 
 		if(newSpatialComponent != nullptr)
 		if(newSpatialComponent != nullptr)
-			oldSpatialComponent = m_entityRegistry.try_get<MetadataComponent>(p_entityID);
+			oldSpatialComponent = m_entityRegistry.try_get<SpatialComponent>(p_entityID);
 
 
 		// Add AUDIO components
 		// Add AUDIO components
 		std::vector<SystemObject *> newAudioComponents = m_sceneLoader->getSystemScene(Systems::Audio)->createComponents(p_entityID, p_constructionInfo, p_startLoading);
 		std::vector<SystemObject *> newAudioComponents = m_sceneLoader->getSystemScene(Systems::Audio)->createComponents(p_entityID, p_constructionInfo, p_startLoading);
@@ -139,6 +139,22 @@ ErrorCode WorldScene::addComponents(const EntityID p_entityID, const ComponentsC
 			{
 			{
 				m_sceneLoader->getChangeController()->createObjectLink(oldScriptingComponents[i], newSpatialComponent);
 				m_sceneLoader->getChangeController()->createObjectLink(oldScriptingComponents[i], newSpatialComponent);
 			}
 			}
+
+			// Link PARENT SPATIAL -> CHILD SPATIAL
+			// Do not process the root node (Entity ID 0)
+			if(auto *metadataComponent = m_entityRegistry.try_get<MetadataComponent>(p_entityID); metadataComponent != nullptr)
+			{
+				if(auto *parentSpatialComponent = m_entityRegistry.try_get<SpatialComponent>(metadataComponent->m_parent); parentSpatialComponent != nullptr && p_entityID != 0)
+				{
+					// Set the parent transform
+					auto *spatialComponent = m_entityRegistry.try_get<SpatialComponent>(p_entityID);
+					spatialComponent->m_spatialData.setParentTransform(parentSpatialComponent->m_spatialData.getWorldTransform());
+					spatialComponent->m_spatialData.update();
+
+					// Link parent to child
+					m_sceneLoader->getChangeController()->createObjectLink(parentSpatialComponent, newSpatialComponent);
+				}
+			}
 		}
 		}
 		else
 		else
 			if(oldSpatialComponent != nullptr)
 			if(oldSpatialComponent != nullptr)
@@ -297,6 +313,24 @@ EntityID WorldScene::createEntity(const ComponentsConstructionInfo &p_constructi
 		{
 		{
 			m_sceneLoader->getChangeController()->createObjectLink(physicsComponents[i], spatialComponent);
 			m_sceneLoader->getChangeController()->createObjectLink(physicsComponents[i], spatialComponent);
 		}
 		}
+
+		// Link PARENT SPATIAL -> CHILD SPATIAL
+		// Do not process the root node (Entity ID 0)
+		if(auto *parentSpatialComponent = m_entityRegistry.try_get<SpatialComponent>(p_constructionInfo.m_parent); parentSpatialComponent != nullptr && newEntity != 0)
+		{
+			// Set the parent transform
+			auto *currentSpatialComponent = m_entityRegistry.try_get<SpatialComponent>(newEntity);
+			currentSpatialComponent->m_spatialData.setParentTransform(parentSpatialComponent->m_spatialData.getWorldTransform());
+			currentSpatialComponent->m_spatialData.update();
+
+			// Link parent to child
+			m_sceneLoader->getChangeController()->createObjectLink(parentSpatialComponent, spatialComponent);
+		}
+		else
+		{
+			if(p_constructionInfo.m_parent != 0)
+				ErrHandlerLoc::get().log(ErrorCode::Nonexistent_parent_entity, p_constructionInfo.m_name, ErrorSource::Source_WorldScene);
+		}
 	}
 	}
 
 
 	// Link SCRIPTING
 	// Link SCRIPTING
@@ -431,7 +465,14 @@ void WorldScene::releaseObject(SystemObject *p_systemObject)
 			{
 			{
 				auto *component = static_cast<SpatialComponent *>(p_systemObject);
 				auto *component = static_cast<SpatialComponent *>(p_systemObject);
 
 
-				// Nothing to release
+				// Unlink parent to child
+				if(auto *metadataComponent = m_entityRegistry.try_get<MetadataComponent>(component->getEntityID()); metadataComponent != nullptr)
+				{
+					if(auto *parentSpatialComponent = m_entityRegistry.try_get<SpatialComponent>(metadataComponent->m_parent); parentSpatialComponent != nullptr)
+					{
+						m_sceneLoader->getChangeController()->removeObjectLink(parentSpatialComponent, component);
+					}
+				}
 			}
 			}
 			break;
 			break;
 	}
 	}

+ 232 - 3
Praxis3D/imgui.ini

@@ -4,7 +4,7 @@ Size=400,400
 Collapsed=0
 Collapsed=0
 
 
 [Window][Dear ImGui Demo]
 [Window][Dear ImGui Demo]
-Pos=76,766
+Pos=104,711
 Size=461,545
 Size=461,545
 Collapsed=1
 Collapsed=1
 
 
@@ -193,7 +193,7 @@ Size=240,71
 Collapsed=0
 Collapsed=0
 
 
 [Window][Open a texture file##OpenTextureFileFileDialog]
 [Window][Open a texture file##OpenTextureFileFileDialog]
-Pos=228,185
+Pos=394,273
 Size=1007,703
 Size=1007,703
 Collapsed=0
 Collapsed=0
 
 
@@ -213,7 +213,7 @@ Size=855,618
 Collapsed=0
 Collapsed=0
 
 
 [Window][##AddEntityPopup]
 [Window][##AddEntityPopup]
-Pos=205,446
+Pos=260,1097
 Size=400,135
 Size=400,135
 Collapsed=0
 Collapsed=0
 
 
@@ -222,6 +222,11 @@ Pos=656,203
 Size=801,605
 Size=801,605
 Collapsed=0
 Collapsed=0
 
 
+[Window][##LoadingStatusWindow]
+Pos=1031,584
+Size=64,70
+Collapsed=0
+
 [Table][0x9A4BDFDE,4]
 [Table][0x9A4BDFDE,4]
 RefScale=13
 RefScale=13
 Column 0  Sort=0v
 Column 0  Sort=0v
@@ -1322,6 +1327,230 @@ Column 0  Sort=0v
 RefScale=13
 RefScale=13
 Column 0  Sort=0v
 Column 0  Sort=0v
 
 
+[Table][0xB2C3E8F1,2]
+Column 0  Weight=1.0000
+Column 1  Weight=1.0000
+
+[Table][0x871AD23E,2]
+Column 0  Weight=1.0000
+Column 1  Weight=1.0000
+
+[Table][0x401C3DE8,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xA959F45E,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xC647F3E5,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x2F023A53,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x548AE92F,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x222F7FFD,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xC9039762,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x9DDE8C35,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x1170D771,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x8F81D2E8,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x16235A02,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x9F48A29A,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x73E4D18C,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x5AD41A44,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x527CFBA7,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xA29744FA,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x950781BF,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x91B9D485,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xB8891F4D,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x27694029,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xCE2C899F,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x35AD74FB,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xD56F0154,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xA3CA9786,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x4A8F5E30,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x58002D06,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x2EA5BBD4,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xCCA85558,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xD408B89C,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xE1FF75F2,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x995F16F6,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x2485C55A,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xBB659A3E,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x52205388,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xF1385856,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x591B5F3E,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x94C1206C,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xA56A3F7B,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x3A8A601F,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xD3CFA9A9,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x0165AA23,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x9E85F547,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x77C03CF1,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xEE0F7F7C,2]
+Column 0  Weight=1.0000
+Column 1  Weight=1.0000
+
+[Table][0x49E7A29A,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x56BBFC21,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xD3B6D8AA,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x4C5687CE,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xA5134E78,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xFA861362,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x5CDF915A,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xDA09EA5D,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xAE7B25A1,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x47797D9D,4]
+RefScale=13
+Column 0  Sort=0v
+
 [Docking][Data]
 [Docking][Data]
 DockSpace         ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,69 Size=2335,1309 Split=X
 DockSpace         ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,69 Size=2335,1309 Split=X
   DockNode        ID=0x00000007 Parent=0x8B93E3BD SizeRef=1409,1011 Split=Y
   DockNode        ID=0x00000007 Parent=0x8B93E3BD SizeRef=1409,1011 Split=Y