Переглянути джерело

Implemented 3D object translation / rotation / scaling tool (ImGuizmo) in EditorWindow, GUIHandler
Added buttons and functionality to delete any component in Inspector in EditorWindow
Added component activation and deactivation in Inspector in EditorWindow
Added buttons to toggle translation and rotation guizmo tool in EditorWindow
Added a button to create a component in Inspector that also checks the available components in EditorWindow
Added functionality to activate / deactivate a component when receiving an Active change in All Components
Added a way to get all created components of an entity that belong to a particular scene in Audio, Graphics, Gui, Physics, Scripting, World scenes
Added a way to delete a component (by sending change notification to a particular scene) in Audio, Graphics, Gui, Physics, Scripting, World scenes
Added a way to get View and Projection matrices from the renderer (required for ImGuizmo) in RendererScene, RendererFrontend
Added a way to create component for existing entities (while also linking the new components with the old components for change tracking) in WorldScene
Added mouse capturing when moving the camera in editor while holding right mouse button in camera lua scripts
Added ComponentType enum storing creatable/deletable components in CommonDefinitions
Added EntityAndComponent container storing the required data for creating/deleting components in Containers
Added more ErrorCodes and error strings
Added more Config variables
Added dependency: ImGuizmo
Fixed a bug of correct flag not being sent when activating / deactivating a Light component because its type is set to DirectionalLight / PointLight / Spotlight instead of LightComponent, in EditorWindow
Fixed a bug of GLM types not being initialized during construction by moving the GLM_FORCE_CTOR_INIT flag to preprocessor definitions
Fixed a bug of the mouse cursor moving outside of the window (when set to relative mode) by the mouse position before going into relative mode and warping it back after leaving relative mode in Window
Fixed a bug of ModelComponent type being set to PropertyID::Models instead of PropertyID::ModelComponent
Fixed a bug of ShaderComponent type being set to PropertyID::Shaders instead of PropertyID::ShaderComponent

Paul A 2 роки тому
батько
коміт
d2ed691686
47 змінених файлів з 4737 додано та 126 видалено
  1. 2996 0
      Dependencies/include/ImGuizmo/ImGuizmo.cpp
  2. 272 0
      Dependencies/include/ImGuizmo/ImGuizmo.h
  3. 7 1
      Praxis3D/Data/Scripts/Camera_free_object_spawn.lua
  4. 4 2
      Praxis3D/Praxis3D.vcxproj
  5. 9 0
      Praxis3D/Praxis3D.vcxproj.filters
  6. 83 0
      Praxis3D/Source/AudioScene.cpp
  7. 6 1
      Praxis3D/Source/AudioScene.h
  8. 1 0
      Praxis3D/Source/CommonDefinitions.cpp
  9. 21 0
      Praxis3D/Source/CommonDefinitions.h
  10. 3 0
      Praxis3D/Source/Config.cpp
  11. 11 2
      Praxis3D/Source/Config.h
  12. 10 0
      Praxis3D/Source/Containers.h
  13. 488 22
      Praxis3D/Source/EditorWindow.cpp
  14. 62 0
      Praxis3D/Source/EditorWindow.h
  15. 2 1
      Praxis3D/Source/ErrorCodes.h
  16. 2 1
      Praxis3D/Source/ErrorHandler.cpp
  17. 5 0
      Praxis3D/Source/GUIHandler.h
  18. 80 34
      Praxis3D/Source/GUIScene.cpp
  19. 3 0
      Praxis3D/Source/GUIScene.h
  20. 3 0
      Praxis3D/Source/GUISequenceComponent.h
  21. 3 3
      Praxis3D/Source/LightComponent.h
  22. 6 0
      Praxis3D/Source/LuaComponent.h
  23. 1 1
      Praxis3D/Source/Math.h
  24. 6 0
      Praxis3D/Source/MetadataComponent.h
  25. 6 2
      Praxis3D/Source/ModelComponent.h
  26. 6 0
      Praxis3D/Source/ObjectMaterialComponent.h
  27. 65 0
      Praxis3D/Source/PhysicsScene.cpp
  28. 4 9
      Praxis3D/Source/PhysicsScene.h
  29. 2 0
      Praxis3D/Source/RendererFrontend.h
  30. 145 34
      Praxis3D/Source/RendererScene.cpp
  31. 5 0
      Praxis3D/Source/RendererScene.h
  32. 6 0
      Praxis3D/Source/RigidBodyComponent.cpp
  33. 34 0
      Praxis3D/Source/RigidBodyComponent.h
  34. 47 0
      Praxis3D/Source/ScriptScene.cpp
  35. 3 0
      Praxis3D/Source/ScriptScene.h
  36. 7 3
      Praxis3D/Source/ShaderComponent.h
  37. 6 0
      Praxis3D/Source/SoundComponent.h
  38. 3 0
      Praxis3D/Source/SoundListenerComponent.h
  39. 6 0
      Praxis3D/Source/SpatialComponent.h
  40. 5 0
      Praxis3D/Source/System.cpp
  41. 3 0
      Praxis3D/Source/System.h
  42. 1 1
      Praxis3D/Source/UniformData.h
  43. 6 2
      Praxis3D/Source/Window.cpp
  44. 11 5
      Praxis3D/Source/Window.h
  45. 237 0
      Praxis3D/Source/WorldScene.cpp
  46. 8 1
      Praxis3D/Source/WorldScene.h
  47. 37 1
      Praxis3D/imgui.ini

+ 2996 - 0
Dependencies/include/ImGuizmo/ImGuizmo.cpp

@@ -0,0 +1,2996 @@
+// https://github.com/CedricGuillemet/ImGuizmo
+// v 1.89 WIP
+//
+// The MIT License(MIT)
+//
+// Copyright(c) 2021 Cedric Guillemet
+//
+// 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.
+//
+
+#ifndef IMGUI_DEFINE_MATH_OPERATORS
+#define IMGUI_DEFINE_MATH_OPERATORS
+#endif
+#include "imgui.h"
+#include "imgui_internal.h"
+#include "ImGuizmo.h"
+
+#if defined(_MSC_VER) || defined(__MINGW32__)
+#include <malloc.h>
+#endif
+#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR)
+#define _malloca(x) alloca(x)
+#define _freea(x)
+#endif
+
+// includes patches for multiview from
+// https://github.com/CedricGuillemet/ImGuizmo/issues/15
+
+namespace IMGUIZMO_NAMESPACE
+{
+   static const float ZPI = 3.14159265358979323846f;
+   static const float RAD2DEG = (180.f / ZPI);
+   static const float DEG2RAD = (ZPI / 180.f);
+   const float screenRotateSize = 0.06f;
+   // scale a bit so translate axis do not touch when in universal
+   const float rotationDisplayFactor = 1.2f;
+
+   static OPERATION operator&(OPERATION lhs, OPERATION rhs)
+   {
+     return static_cast<OPERATION>(static_cast<int>(lhs) & static_cast<int>(rhs));
+   }
+
+   static bool operator!=(OPERATION lhs, int rhs)
+   {
+     return static_cast<int>(lhs) != rhs;
+   }
+
+   static bool Intersects(OPERATION lhs, OPERATION rhs)
+   {
+     return (lhs & rhs) != 0;
+   }
+
+   // True if lhs contains rhs
+   static bool Contains(OPERATION lhs, OPERATION rhs)
+   {
+     return (lhs & rhs) == rhs;
+   }
+
+   ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   // utility and math
+
+   void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r)
+   {
+      r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12];
+      r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13];
+      r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14];
+      r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15];
+
+      r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12];
+      r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13];
+      r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14];
+      r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15];
+
+      r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12];
+      r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13];
+      r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14];
+      r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15];
+
+      r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12];
+      r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13];
+      r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14];
+      r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15];
+   }
+
+   void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16)
+   {
+      float temp, temp2, temp3, temp4;
+      temp = 2.0f * znear;
+      temp2 = right - left;
+      temp3 = top - bottom;
+      temp4 = zfar - znear;
+      m16[0] = temp / temp2;
+      m16[1] = 0.0;
+      m16[2] = 0.0;
+      m16[3] = 0.0;
+      m16[4] = 0.0;
+      m16[5] = temp / temp3;
+      m16[6] = 0.0;
+      m16[7] = 0.0;
+      m16[8] = (right + left) / temp2;
+      m16[9] = (top + bottom) / temp3;
+      m16[10] = (-zfar - znear) / temp4;
+      m16[11] = -1.0f;
+      m16[12] = 0.0;
+      m16[13] = 0.0;
+      m16[14] = (-temp * zfar) / temp4;
+      m16[15] = 0.0;
+   }
+
+   void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16)
+   {
+      float ymax, xmax;
+      ymax = znear * tanf(fovyInDegrees * DEG2RAD);
+      xmax = ymax * aspectRatio;
+      Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16);
+   }
+
+   void Cross(const float* a, const float* b, float* r)
+   {
+      r[0] = a[1] * b[2] - a[2] * b[1];
+      r[1] = a[2] * b[0] - a[0] * b[2];
+      r[2] = a[0] * b[1] - a[1] * b[0];
+   }
+
+   float Dot(const float* a, const float* b)
+   {
+      return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+   }
+
+   void Normalize(const float* a, float* r)
+   {
+      float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON);
+      r[0] = a[0] * il;
+      r[1] = a[1] * il;
+      r[2] = a[2] * il;
+   }
+
+   void LookAt(const float* eye, const float* at, const float* up, float* m16)
+   {
+      float X[3], Y[3], Z[3], tmp[3];
+
+      tmp[0] = eye[0] - at[0];
+      tmp[1] = eye[1] - at[1];
+      tmp[2] = eye[2] - at[2];
+      Normalize(tmp, Z);
+      Normalize(up, Y);
+      Cross(Y, Z, tmp);
+      Normalize(tmp, X);
+      Cross(Z, X, tmp);
+      Normalize(tmp, Y);
+
+      m16[0] = X[0];
+      m16[1] = Y[0];
+      m16[2] = Z[0];
+      m16[3] = 0.0f;
+      m16[4] = X[1];
+      m16[5] = Y[1];
+      m16[6] = Z[1];
+      m16[7] = 0.0f;
+      m16[8] = X[2];
+      m16[9] = Y[2];
+      m16[10] = Z[2];
+      m16[11] = 0.0f;
+      m16[12] = -Dot(X, eye);
+      m16[13] = -Dot(Y, eye);
+      m16[14] = -Dot(Z, eye);
+      m16[15] = 1.0f;
+   }
+
+   template <typename T> T Clamp(T x, T y, T z) { return ((x < y) ? y : ((x > z) ? z : x)); }
+   template <typename T> T max(T x, T y) { return (x > y) ? x : y; }
+   template <typename T> T min(T x, T y) { return (x < y) ? x : y; }
+   template <typename T> bool IsWithin(T x, T y, T z) { return (x >= y) && (x <= z); }
+
+   struct matrix_t;
+   struct vec_t
+   {
+   public:
+      float x, y, z, w;
+
+      void Lerp(const vec_t& v, float t)
+      {
+         x += (v.x - x) * t;
+         y += (v.y - y) * t;
+         z += (v.z - z) * t;
+         w += (v.w - w) * t;
+      }
+
+      void Set(float v) { x = y = z = w = v; }
+      void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; }
+
+      vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; }
+      vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; }
+      vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; }
+      vec_t& operator *= (float v) { x *= v;    y *= v;    z *= v;    w *= v;    return *this; }
+
+      vec_t operator * (float f) const;
+      vec_t operator - () const;
+      vec_t operator - (const vec_t& v) const;
+      vec_t operator + (const vec_t& v) const;
+      vec_t operator * (const vec_t& v) const;
+
+      const vec_t& operator + () const { return (*this); }
+      float Length() const { return sqrtf(x * x + y * y + z * z); };
+      float LengthSq() const { return (x * x + y * y + z * z); };
+      vec_t Normalize() { (*this) *= (1.f / ( Length() > FLT_EPSILON ? Length() : FLT_EPSILON ) ); return (*this); }
+      vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); }
+      vec_t Abs() const;
+
+      void Cross(const vec_t& v)
+      {
+         vec_t res;
+         res.x = y * v.z - z * v.y;
+         res.y = z * v.x - x * v.z;
+         res.z = x * v.y - y * v.x;
+
+         x = res.x;
+         y = res.y;
+         z = res.z;
+         w = 0.f;
+      }
+
+      void Cross(const vec_t& v1, const vec_t& v2)
+      {
+         x = v1.y * v2.z - v1.z * v2.y;
+         y = v1.z * v2.x - v1.x * v2.z;
+         z = v1.x * v2.y - v1.y * v2.x;
+         w = 0.f;
+      }
+
+      float Dot(const vec_t& v) const
+      {
+         return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w);
+      }
+
+      float Dot3(const vec_t& v) const
+      {
+         return (x * v.x) + (y * v.y) + (z * v.z);
+      }
+
+      void Transform(const matrix_t& matrix);
+      void Transform(const vec_t& s, const matrix_t& matrix);
+
+      void TransformVector(const matrix_t& matrix);
+      void TransformPoint(const matrix_t& matrix);
+      void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); }
+      void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); }
+
+      float& operator [] (size_t index) { return ((float*)&x)[index]; }
+      const float& operator [] (size_t index) const { return ((float*)&x)[index]; }
+      bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)) != 0; }
+   };
+
+   vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; }
+   vec_t makeVect(ImVec2 v) { vec_t res; res.x = v.x; res.y = v.y; res.z = 0.f; res.w = 0.f; return res; }
+   vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w * f); }
+   vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); }
+   vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); }
+   vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); }
+   vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); }
+   vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); }
+
+   vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; }
+   vec_t Cross(const vec_t& v1, const vec_t& v2)
+   {
+      vec_t res;
+      res.x = v1.y * v2.z - v1.z * v2.y;
+      res.y = v1.z * v2.x - v1.x * v2.z;
+      res.z = v1.x * v2.y - v1.y * v2.x;
+      res.w = 0.f;
+      return res;
+   }
+
+   float Dot(const vec_t& v1, const vec_t& v2)
+   {
+      return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z);
+   }
+
+   vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal)
+   {
+      vec_t normal, res;
+      normal.Normalize(p_normal);
+      res.w = normal.Dot(p_point1);
+      res.x = normal.x;
+      res.y = normal.y;
+      res.z = normal.z;
+      return res;
+   }
+
+   struct matrix_t
+   {
+   public:
+
+      union
+      {
+         float m[4][4];
+         float m16[16];
+         struct
+         {
+            vec_t right, up, dir, position;
+         } v;
+         vec_t component[4];
+      };
+
+      operator float* () { return m16; }
+      operator const float* () const { return m16; }
+      void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); }
+
+      void Translation(const vec_t& vt)
+      {
+         v.right.Set(1.f, 0.f, 0.f, 0.f);
+         v.up.Set(0.f, 1.f, 0.f, 0.f);
+         v.dir.Set(0.f, 0.f, 1.f, 0.f);
+         v.position.Set(vt.x, vt.y, vt.z, 1.f);
+      }
+
+      void Scale(float _x, float _y, float _z)
+      {
+         v.right.Set(_x, 0.f, 0.f, 0.f);
+         v.up.Set(0.f, _y, 0.f, 0.f);
+         v.dir.Set(0.f, 0.f, _z, 0.f);
+         v.position.Set(0.f, 0.f, 0.f, 1.f);
+      }
+      void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); }
+
+      matrix_t& operator *= (const matrix_t& mat)
+      {
+         matrix_t tmpMat;
+         tmpMat = *this;
+         tmpMat.Multiply(mat);
+         *this = tmpMat;
+         return *this;
+      }
+      matrix_t operator * (const matrix_t& mat) const
+      {
+         matrix_t matT;
+         matT.Multiply(*this, mat);
+         return matT;
+      }
+
+      void Multiply(const matrix_t& matrix)
+      {
+         matrix_t tmp;
+         tmp = *this;
+
+         FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this);
+      }
+
+      void Multiply(const matrix_t& m1, const matrix_t& m2)
+      {
+         FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this);
+      }
+
+      float GetDeterminant() const
+      {
+         return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] -
+            m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1];
+      }
+
+      float Inverse(const matrix_t& srcMatrix, bool affine = false);
+      void SetToIdentity()
+      {
+         v.right.Set(1.f, 0.f, 0.f, 0.f);
+         v.up.Set(0.f, 1.f, 0.f, 0.f);
+         v.dir.Set(0.f, 0.f, 1.f, 0.f);
+         v.position.Set(0.f, 0.f, 0.f, 1.f);
+      }
+      void Transpose()
+      {
+         matrix_t tmpm;
+         for (int l = 0; l < 4; l++)
+         {
+            for (int c = 0; c < 4; c++)
+            {
+               tmpm.m[l][c] = m[c][l];
+            }
+         }
+         (*this) = tmpm;
+      }
+
+      void RotationAxis(const vec_t& axis, float angle);
+
+      void OrthoNormalize()
+      {
+         v.right.Normalize();
+         v.up.Normalize();
+         v.dir.Normalize();
+      }
+   };
+
+   void vec_t::Transform(const matrix_t& matrix)
+   {
+      vec_t out;
+
+      out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0];
+      out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1];
+      out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2];
+      out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3];
+
+      x = out.x;
+      y = out.y;
+      z = out.z;
+      w = out.w;
+   }
+
+   void vec_t::Transform(const vec_t& s, const matrix_t& matrix)
+   {
+      *this = s;
+      Transform(matrix);
+   }
+
+   void vec_t::TransformPoint(const matrix_t& matrix)
+   {
+      vec_t out;
+
+      out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0];
+      out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1];
+      out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2];
+      out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3];
+
+      x = out.x;
+      y = out.y;
+      z = out.z;
+      w = out.w;
+   }
+
+   void vec_t::TransformVector(const matrix_t& matrix)
+   {
+      vec_t out;
+
+      out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0];
+      out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1];
+      out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2];
+      out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3];
+
+      x = out.x;
+      y = out.y;
+      z = out.z;
+      w = out.w;
+   }
+
+   float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine)
+   {
+      float det = 0;
+
+      if (affine)
+      {
+         det = GetDeterminant();
+         float s = 1 / det;
+         m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s;
+         m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s;
+         m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s;
+         m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s;
+         m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s;
+         m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s;
+         m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s;
+         m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s;
+         m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s;
+         m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]);
+         m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]);
+         m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]);
+      }
+      else
+      {
+         // transpose matrix
+         float src[16];
+         for (int i = 0; i < 4; ++i)
+         {
+            src[i] = srcMatrix.m16[i * 4];
+            src[i + 4] = srcMatrix.m16[i * 4 + 1];
+            src[i + 8] = srcMatrix.m16[i * 4 + 2];
+            src[i + 12] = srcMatrix.m16[i * 4 + 3];
+         }
+
+         // calculate pairs for first 8 elements (cofactors)
+         float tmp[12]; // temp array for pairs
+         tmp[0] = src[10] * src[15];
+         tmp[1] = src[11] * src[14];
+         tmp[2] = src[9] * src[15];
+         tmp[3] = src[11] * src[13];
+         tmp[4] = src[9] * src[14];
+         tmp[5] = src[10] * src[13];
+         tmp[6] = src[8] * src[15];
+         tmp[7] = src[11] * src[12];
+         tmp[8] = src[8] * src[14];
+         tmp[9] = src[10] * src[12];
+         tmp[10] = src[8] * src[13];
+         tmp[11] = src[9] * src[12];
+
+         // calculate first 8 elements (cofactors)
+         m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]);
+         m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]);
+         m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]);
+         m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]);
+         m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]);
+         m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]);
+         m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]);
+         m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]);
+
+         // calculate pairs for second 8 elements (cofactors)
+         tmp[0] = src[2] * src[7];
+         tmp[1] = src[3] * src[6];
+         tmp[2] = src[1] * src[7];
+         tmp[3] = src[3] * src[5];
+         tmp[4] = src[1] * src[6];
+         tmp[5] = src[2] * src[5];
+         tmp[6] = src[0] * src[7];
+         tmp[7] = src[3] * src[4];
+         tmp[8] = src[0] * src[6];
+         tmp[9] = src[2] * src[4];
+         tmp[10] = src[0] * src[5];
+         tmp[11] = src[1] * src[4];
+
+         // calculate second 8 elements (cofactors)
+         m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]);
+         m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]);
+         m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]);
+         m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]);
+         m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]);
+         m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]);
+         m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]);
+         m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]);
+
+         // calculate determinant
+         det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3];
+
+         // calculate matrix inverse
+         float invdet = 1 / det;
+         for (int j = 0; j < 16; ++j)
+         {
+            m16[j] *= invdet;
+         }
+      }
+
+      return det;
+   }
+
+   void matrix_t::RotationAxis(const vec_t& axis, float angle)
+   {
+      float length2 = axis.LengthSq();
+      if (length2 < FLT_EPSILON)
+      {
+         SetToIdentity();
+         return;
+      }
+
+      vec_t n = axis * (1.f / sqrtf(length2));
+      float s = sinf(angle);
+      float c = cosf(angle);
+      float k = 1.f - c;
+
+      float xx = n.x * n.x * k + c;
+      float yy = n.y * n.y * k + c;
+      float zz = n.z * n.z * k + c;
+      float xy = n.x * n.y * k;
+      float yz = n.y * n.z * k;
+      float zx = n.z * n.x * k;
+      float xs = n.x * s;
+      float ys = n.y * s;
+      float zs = n.z * s;
+
+      m[0][0] = xx;
+      m[0][1] = xy + zs;
+      m[0][2] = zx - ys;
+      m[0][3] = 0.f;
+      m[1][0] = xy - zs;
+      m[1][1] = yy;
+      m[1][2] = yz + xs;
+      m[1][3] = 0.f;
+      m[2][0] = zx + ys;
+      m[2][1] = yz - xs;
+      m[2][2] = zz;
+      m[2][3] = 0.f;
+      m[3][0] = 0.f;
+      m[3][1] = 0.f;
+      m[3][2] = 0.f;
+      m[3][3] = 1.f;
+   }
+
+   ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   //
+
+   enum MOVETYPE
+   {
+      MT_NONE,
+      MT_MOVE_X,
+      MT_MOVE_Y,
+      MT_MOVE_Z,
+      MT_MOVE_YZ,
+      MT_MOVE_ZX,
+      MT_MOVE_XY,
+      MT_MOVE_SCREEN,
+      MT_ROTATE_X,
+      MT_ROTATE_Y,
+      MT_ROTATE_Z,
+      MT_ROTATE_SCREEN,
+      MT_SCALE_X,
+      MT_SCALE_Y,
+      MT_SCALE_Z,
+      MT_SCALE_XYZ
+   };
+
+   static bool IsTranslateType(int type)
+   {
+     return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN;
+   }
+
+   static bool IsRotateType(int type)
+   {
+     return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN;
+   }
+
+   static bool IsScaleType(int type)
+   {
+     return type >= MT_SCALE_X && type <= MT_SCALE_XYZ;
+   }
+
+   // Matches MT_MOVE_AB order
+   static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y };
+
+   Style::Style()
+   {
+      // default values
+      TranslationLineThickness   = 3.0f;
+      TranslationLineArrowSize   = 6.0f;
+      RotationLineThickness      = 2.0f;
+      RotationOuterLineThickness = 3.0f;
+      ScaleLineThickness         = 3.0f;
+      ScaleLineCircleSize        = 6.0f;
+      HatchedAxisLineThickness   = 6.0f;
+      CenterCircleSize           = 6.0f;
+
+      // initialize default colors
+      Colors[DIRECTION_X]           = ImVec4(0.666f, 0.000f, 0.000f, 1.000f);
+      Colors[DIRECTION_Y]           = ImVec4(0.000f, 0.666f, 0.000f, 1.000f);
+      Colors[DIRECTION_Z]           = ImVec4(0.000f, 0.000f, 0.666f, 1.000f);
+      Colors[PLANE_X]               = ImVec4(0.666f, 0.000f, 0.000f, 0.380f);
+      Colors[PLANE_Y]               = ImVec4(0.000f, 0.666f, 0.000f, 0.380f);
+      Colors[PLANE_Z]               = ImVec4(0.000f, 0.000f, 0.666f, 0.380f);
+      Colors[SELECTION]             = ImVec4(1.000f, 0.500f, 0.062f, 0.541f);
+      Colors[INACTIVE]              = ImVec4(0.600f, 0.600f, 0.600f, 0.600f);
+      Colors[TRANSLATION_LINE]      = ImVec4(0.666f, 0.666f, 0.666f, 0.666f);
+      Colors[SCALE_LINE]            = ImVec4(0.250f, 0.250f, 0.250f, 1.000f);
+      Colors[ROTATION_USING_BORDER] = ImVec4(1.000f, 0.500f, 0.062f, 1.000f);
+      Colors[ROTATION_USING_FILL]   = ImVec4(1.000f, 0.500f, 0.062f, 0.500f);
+      Colors[HATCHED_AXIS_LINES]    = ImVec4(0.000f, 0.000f, 0.000f, 0.500f);
+      Colors[TEXT]                  = ImVec4(1.000f, 1.000f, 1.000f, 1.000f);
+      Colors[TEXT_SHADOW]           = ImVec4(0.000f, 0.000f, 0.000f, 1.000f);
+   }
+
+   struct Context
+   {
+      Context() : mbUsing(false), mbEnable(true), mbUsingBounds(false)
+      {
+      }
+
+      ImDrawList* mDrawList;
+      Style mStyle;
+
+      MODE mMode;
+      matrix_t mViewMat;
+      matrix_t mProjectionMat;
+      matrix_t mModel;
+      matrix_t mModelLocal; // orthonormalized model
+      matrix_t mModelInverse;
+      matrix_t mModelSource;
+      matrix_t mModelSourceInverse;
+      matrix_t mMVP;
+      matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition
+      matrix_t mViewProjection;
+
+      vec_t mModelScaleOrigin;
+      vec_t mCameraEye;
+      vec_t mCameraRight;
+      vec_t mCameraDir;
+      vec_t mCameraUp;
+      vec_t mRayOrigin;
+      vec_t mRayVector;
+
+      float  mRadiusSquareCenter;
+      ImVec2 mScreenSquareCenter;
+      ImVec2 mScreenSquareMin;
+      ImVec2 mScreenSquareMax;
+
+      float mScreenFactor;
+      vec_t mRelativeOrigin;
+
+      bool mbUsing;
+      bool mbEnable;
+      bool mbMouseOver;
+      bool mReversed; // reversed projection matrix
+
+      // translation
+      vec_t mTranslationPlan;
+      vec_t mTranslationPlanOrigin;
+      vec_t mMatrixOrigin;
+      vec_t mTranslationLastDelta;
+
+      // rotation
+      vec_t mRotationVectorSource;
+      float mRotationAngle;
+      float mRotationAngleOrigin;
+      //vec_t mWorldToLocalAxis;
+
+      // scale
+      vec_t mScale;
+      vec_t mScaleValueOrigin;
+      vec_t mScaleLast;
+      float mSaveMousePosx;
+
+      // save axis factor when using gizmo
+      bool mBelowAxisLimit[3];
+      bool mBelowPlaneLimit[3];
+      float mAxisFactor[3];
+
+      float mAxisLimit=0.0025f;
+      float mPlaneLimit=0.02f;
+
+      // bounds stretching
+      vec_t mBoundsPivot;
+      vec_t mBoundsAnchor;
+      vec_t mBoundsPlan;
+      vec_t mBoundsLocalPivot;
+      int mBoundsBestAxis;
+      int mBoundsAxis[2];
+      bool mbUsingBounds;
+      matrix_t mBoundsMatrix;
+
+      //
+      int mCurrentOperation;
+
+      float mX = 0.f;
+      float mY = 0.f;
+      float mWidth = 0.f;
+      float mHeight = 0.f;
+      float mXMax = 0.f;
+      float mYMax = 0.f;
+      float mDisplayRatio = 1.f;
+
+      bool mIsOrthographic = false;
+
+      int mActualID = -1;
+      int mEditingID = -1;
+      OPERATION mOperation = OPERATION(-1);
+
+      bool mAllowAxisFlip = true;
+      float mGizmoSizeClipSpace = 0.1f;
+   };
+
+   static Context gContext;
+
+   static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) };
+   static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f",
+      "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f",
+      "X : %5.3f Y : %5.3f Z : %5.3f" };
+   static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" };
+   static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" };
+   static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 };
+   static const float quadMin = 0.5f;
+   static const float quadMax = 0.8f;
+   static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin };
+   static const int halfCircleSegmentCount = 64;
+   static const float snapTension = 0.5f;
+
+   ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   //
+   static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion);
+   static int GetRotateType(OPERATION op);
+   static int GetScaleType(OPERATION op);
+
+   Style& GetStyle()
+   {
+      return gContext.mStyle;
+   }
+
+   static ImU32 GetColorU32(int idx)
+   {
+      IM_ASSERT(idx < COLOR::COUNT);
+      return ImGui::ColorConvertFloat4ToU32(gContext.mStyle.Colors[idx]);
+   }
+
+   static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight))
+   {
+      vec_t trans;
+      trans.TransformPoint(worldPos, mat);
+      trans *= 0.5f / trans.w;
+      trans += makeVect(0.5f, 0.5f);
+      trans.y = 1.f - trans.y;
+      trans.x *= size.x;
+      trans.y *= size.y;
+      trans.x += position.x;
+      trans.y += position.y;
+      return ImVec2(trans.x, trans.y);
+   }
+
+   static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight))
+   {
+      ImGuiIO& io = ImGui::GetIO();
+
+      matrix_t mViewProjInverse;
+      mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat);
+
+      const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f;
+      const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f;
+
+      const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f;
+      const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON);
+
+      rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse);
+      rayOrigin *= 1.f / rayOrigin.w;
+      vec_t rayEnd;
+      rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse);
+      rayEnd *= 1.f / rayEnd.w;
+      rayDir = Normalized(rayEnd - rayOrigin);
+   }
+
+   static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end, const bool localCoordinates = false)
+   {
+      vec_t startOfSegment = start;
+      const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP;
+      startOfSegment.TransformPoint(mvp);
+      if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction
+      {
+         startOfSegment *= 1.f / startOfSegment.w;
+      }
+
+      vec_t endOfSegment = end;
+      endOfSegment.TransformPoint(mvp);
+      if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction
+      {
+         endOfSegment *= 1.f / endOfSegment.w;
+      }
+
+      vec_t clipSpaceAxis = endOfSegment - startOfSegment;
+      if (gContext.mDisplayRatio < 1.0)
+         clipSpaceAxis.x *= gContext.mDisplayRatio;
+      else
+         clipSpaceAxis.y /= gContext.mDisplayRatio;
+      float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y);
+      return segmentLengthInClipSpace;
+   }
+
+   static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB)
+   {
+      vec_t pts[] = { ptO, ptA, ptB };
+      for (unsigned int i = 0; i < 3; i++)
+      {
+         pts[i].TransformPoint(gContext.mMVP);
+         if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction
+         {
+            pts[i] *= 1.f / pts[i].w;
+         }
+      }
+      vec_t segA = pts[1] - pts[0];
+      vec_t segB = pts[2] - pts[0];
+      segA.y /= gContext.mDisplayRatio;
+      segB.y /= gContext.mDisplayRatio;
+      vec_t segAOrtho = makeVect(-segA.y, segA.x);
+      segAOrtho.Normalize();
+      float dt = segAOrtho.Dot3(segB);
+      float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt);
+      return surface;
+   }
+
+   inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2)
+   {
+      vec_t c = point - vertPos1;
+      vec_t V;
+
+      V.Normalize(vertPos2 - vertPos1);
+      float d = (vertPos2 - vertPos1).Length();
+      float t = V.Dot3(c);
+
+      if (t < 0.f)
+      {
+         return vertPos1;
+      }
+
+      if (t > d)
+      {
+         return vertPos2;
+      }
+
+      return vertPos1 + V * t;
+   }
+
+   static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan)
+   {
+      const float numer = plan.Dot3(rOrigin) - plan.w;
+      const float denom = plan.Dot3(rVector);
+
+      if (fabsf(denom) < FLT_EPSILON)  // normal is orthogonal to vector, cant intersect
+      {
+         return -1.0f;
+      }
+
+      return -(numer / denom);
+   }
+
+   static float DistanceToPlane(const vec_t& point, const vec_t& plan)
+   {
+      return plan.Dot3(point) + plan.w;
+   }
+
+   static bool IsInContextRect(ImVec2 p)
+   {
+      return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax);
+   }
+
+   static bool IsHoveringWindow()
+   {
+      ImGuiContext& g = *ImGui::GetCurrentContext();
+      ImGuiWindow* window = ImGui::FindWindowByName(gContext.mDrawList->_OwnerName);
+      if (g.HoveredWindow == window)   // Mouse hovering drawlist window
+         return true;
+      if (g.HoveredWindow != NULL)     // Any other window is hovered
+         return false;
+      if (ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max, false))   // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows)
+         return true;
+      return false;
+   }
+
+   void SetRect(float x, float y, float width, float height)
+   {
+      gContext.mX = x;
+      gContext.mY = y;
+      gContext.mWidth = width;
+      gContext.mHeight = height;
+      gContext.mXMax = gContext.mX + gContext.mWidth;
+      gContext.mYMax = gContext.mY + gContext.mXMax;
+      gContext.mDisplayRatio = width / height;
+   }
+
+   void SetOrthographic(bool isOrthographic)
+   {
+      gContext.mIsOrthographic = isOrthographic;
+   }
+
+   void SetDrawlist(ImDrawList* drawlist)
+   {
+      gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList();
+   }
+
+   void SetImGuiContext(ImGuiContext* ctx)
+   {
+      ImGui::SetCurrentContext(ctx);
+   }
+
+   void BeginFrame()
+   {
+      const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus;
+
+#ifdef IMGUI_HAS_VIEWPORT
+      ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size);
+      ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos);
+#else
+      ImGuiIO& io = ImGui::GetIO();
+      ImGui::SetNextWindowSize(io.DisplaySize);
+      ImGui::SetNextWindowPos(ImVec2(0, 0));
+#endif
+
+      ImGui::PushStyleColor(ImGuiCol_WindowBg, 0);
+      ImGui::PushStyleColor(ImGuiCol_Border, 0);
+      ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
+
+      ImGui::Begin("gizmo", NULL, flags);
+      gContext.mDrawList = ImGui::GetWindowDrawList();
+      ImGui::End();
+      ImGui::PopStyleVar();
+      ImGui::PopStyleColor(2);
+   }
+
+   bool IsUsing()
+   {
+      return (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) || gContext.mbUsingBounds;
+   }
+
+   bool IsUsingAny()
+   {
+      return gContext.mbUsing || gContext.mbUsingBounds;
+   }
+
+   bool IsOver()
+   {
+      return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) ||
+         (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) ||
+         (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing();
+   }
+
+   bool IsOver(OPERATION op)
+   {
+      if(IsUsing())
+      {
+         return true;
+      }
+      if(Intersects(op, SCALE) && GetScaleType(op) != MT_NONE)
+      {
+         return true;
+      }
+      if(Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE)
+      {
+         return true;
+      }
+      if(Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE)
+      {
+         return true;
+      }
+      return false;
+   }
+
+   void Enable(bool enable)
+   {
+      gContext.mbEnable = enable;
+      if (!enable)
+      {
+         gContext.mbUsing = false;
+         gContext.mbUsingBounds = false;
+      }
+   }
+
+   static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode)
+   {
+      gContext.mMode = mode;
+      gContext.mViewMat = *(matrix_t*)view;
+      gContext.mProjectionMat = *(matrix_t*)projection;
+      gContext.mbMouseOver = IsHoveringWindow();
+
+      gContext.mModelLocal = *(matrix_t*)matrix;
+      gContext.mModelLocal.OrthoNormalize();
+
+      if (mode == LOCAL)
+      {
+         gContext.mModel = gContext.mModelLocal;
+      }
+      else
+      {
+         gContext.mModel.Translation(((matrix_t*)matrix)->v.position);
+      }
+      gContext.mModelSource = *(matrix_t*)matrix;
+      gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length());
+
+      gContext.mModelInverse.Inverse(gContext.mModel);
+      gContext.mModelSourceInverse.Inverse(gContext.mModelSource);
+      gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat;
+      gContext.mMVP = gContext.mModel * gContext.mViewProjection;
+      gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection;
+
+      matrix_t viewInverse;
+      viewInverse.Inverse(gContext.mViewMat);
+      gContext.mCameraDir = viewInverse.v.dir;
+      gContext.mCameraEye = viewInverse.v.position;
+      gContext.mCameraRight = viewInverse.v.right;
+      gContext.mCameraUp = viewInverse.v.up;
+
+      // projection reverse
+       vec_t nearPos, farPos;
+       nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat);
+       farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat);
+
+       gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w);
+
+      // compute scale from the size of camera right vector projected on screen at the matrix position
+      vec_t pointRight = viewInverse.v.right;
+      pointRight.TransformPoint(gContext.mViewProjection);
+      gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / (pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w);
+
+      vec_t rightViewInverse = viewInverse.v.right;
+      rightViewInverse.TransformVector(gContext.mModelInverse);
+      float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse);
+      gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength;
+
+      ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP);
+      gContext.mScreenSquareCenter = centerSSpace;
+      gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f);
+      gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f);
+
+      ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector);
+   }
+
+   static void ComputeColors(ImU32* colors, int type, OPERATION operation)
+   {
+      if (gContext.mbEnable)
+      {
+         ImU32 selectionColor = GetColorU32(SELECTION);
+
+         switch (operation)
+         {
+         case TRANSLATE:
+            colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE;
+            for (int i = 0; i < 3; i++)
+            {
+               colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i);
+               colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : GetColorU32(PLANE_X + i);
+               colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4];
+            }
+            break;
+         case ROTATE:
+            colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE;
+            for (int i = 0; i < 3; i++)
+            {
+               colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i);
+            }
+            break;
+         case SCALEU:
+         case SCALE:
+            colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE;
+            for (int i = 0; i < 3; i++)
+            {
+               colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i);
+            }
+            break;
+         // note: this internal function is only called with three possible values for operation
+         default:
+            break;
+         }
+      }
+      else
+      {
+         ImU32 inactiveColor = GetColorU32(INACTIVE);
+         for (int i = 0; i < 7; i++)
+         {
+            colors[i] = inactiveColor;
+         }
+      }
+   }
+
+   static void ComputeTripodAxisAndVisibility(const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false)
+   {
+      dirAxis = directionUnary[axisIndex];
+      dirPlaneX = directionUnary[(axisIndex + 1) % 3];
+      dirPlaneY = directionUnary[(axisIndex + 2) % 3];
+
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID))
+      {
+         // when using, use stored factors so the gizmo doesn't flip when we translate
+         belowAxisLimit = gContext.mBelowAxisLimit[axisIndex];
+         belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex];
+
+         dirAxis *= gContext.mAxisFactor[axisIndex];
+         dirPlaneX *= gContext.mAxisFactor[(axisIndex + 1) % 3];
+         dirPlaneY *= gContext.mAxisFactor[(axisIndex + 2) % 3];
+      }
+      else
+      {
+         // new method
+         float lenDir = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis, localCoordinates);
+         float lenDirMinus = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirAxis, localCoordinates);
+
+         float lenDirPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneX, localCoordinates);
+         float lenDirMinusPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneX, localCoordinates);
+
+         float lenDirPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneY, localCoordinates);
+         float lenDirMinusPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneY, localCoordinates);
+
+         // For readability
+         bool & allowFlip = gContext.mAllowAxisFlip;
+         float mulAxis = (allowFlip && lenDir < lenDirMinus&& fabsf(lenDir - lenDirMinus) > FLT_EPSILON) ? -1.f : 1.f;
+         float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX&& fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f;
+         float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY&& fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f;
+         dirAxis *= mulAxis;
+         dirPlaneX *= mulAxisX;
+         dirPlaneY *= mulAxisY;
+
+         // for axis
+         float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor, localCoordinates);
+
+         float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor);
+         belowPlaneLimit = (paraSurf > gContext.mAxisLimit);
+         belowAxisLimit = (axisLengthInClipSpace > gContext.mPlaneLimit);
+
+         // and store values
+         gContext.mAxisFactor[axisIndex] = mulAxis;
+         gContext.mAxisFactor[(axisIndex + 1) % 3] = mulAxisX;
+         gContext.mAxisFactor[(axisIndex + 2) % 3] = mulAxisY;
+         gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit;
+         gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit;
+      }
+   }
+
+   static void ComputeSnap(float* value, float snap)
+   {
+      if (snap <= FLT_EPSILON)
+      {
+         return;
+      }
+
+      float modulo = fmodf(*value, snap);
+      float moduloRatio = fabsf(modulo) / snap;
+      if (moduloRatio < snapTension)
+      {
+         *value -= modulo;
+      }
+      else if (moduloRatio > (1.f - snapTension))
+      {
+         *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f);
+      }
+   }
+   static void ComputeSnap(vec_t& value, const float* snap)
+   {
+      for (int i = 0; i < 3; i++)
+      {
+         ComputeSnap(&value[i], snap[i]);
+      }
+   }
+
+   static float ComputeAngleOnPlan()
+   {
+      const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan);
+      vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position);
+
+      vec_t perpendicularVector;
+      perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan);
+      perpendicularVector.Normalize();
+      float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f);
+      float angle = acosf(acosAngle);
+      angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f;
+      return angle;
+   }
+
+   static void DrawRotationGizmo(OPERATION op, int type)
+   {
+      if(!Intersects(op, ROTATE))
+      {
+         return;
+      }
+      ImDrawList* drawList = gContext.mDrawList;
+
+      // colors
+      ImU32 colors[7];
+      ComputeColors(colors, type, ROTATE);
+
+      vec_t cameraToModelNormalized;
+      if (gContext.mIsOrthographic)
+      {
+         matrix_t viewInverse;
+         viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat);
+         cameraToModelNormalized = -viewInverse.v.dir;
+      }
+      else
+      {
+         cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye);
+      }
+
+      cameraToModelNormalized.TransformVector(gContext.mModelInverse);
+
+      gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight;
+
+      bool hasRSC = Intersects(op, ROTATE_SCREEN);
+      for (int axis = 0; axis < 3; axis++)
+      {
+         if(!Intersects(op, static_cast<OPERATION>(ROTATE_Z >> axis)))
+         {
+            continue;
+         }
+         const bool usingAxis = (gContext.mbUsing && type == MT_ROTATE_Z - axis);
+         const int circleMul = (hasRSC && !usingAxis ) ? 1 : 2;
+
+         ImVec2* circlePos = (ImVec2*)alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1));
+
+         float angleStart = atan2f(cameraToModelNormalized[(4 - axis) % 3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f;
+
+         for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++)
+         {
+            float ng = angleStart + (float)circleMul * ZPI * ((float)i / (float)(circleMul * halfCircleSegmentCount));
+            vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f);
+            vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor * rotationDisplayFactor;
+            circlePos[i] = worldToPos(pos, gContext.mMVP);
+         }
+         if (!gContext.mbUsing || usingAxis)
+         {
+            drawList->AddPolyline(circlePos, circleMul* halfCircleSegmentCount + 1, colors[3 - axis], false, gContext.mStyle.RotationLineThickness);
+         }
+
+         float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0])));
+         if (radiusAxis > gContext.mRadiusSquareCenter)
+         {
+            gContext.mRadiusSquareCenter = radiusAxis;
+         }
+      }
+      if(hasRSC && (!gContext.mbUsing || type == MT_ROTATE_SCREEN))
+      {
+         drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, gContext.mStyle.RotationOuterLineThickness);
+      }
+
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(type))
+      {
+         ImVec2 circlePos[halfCircleSegmentCount + 1];
+
+         circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection);
+         for (unsigned int i = 1; i < halfCircleSegmentCount + 1; i++)
+         {
+            float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1));
+            matrix_t rotateVectorMatrix;
+            rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng);
+            vec_t pos;
+            pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix);
+            pos *= gContext.mScreenFactor * rotationDisplayFactor;
+            circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection);
+         }
+         drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_FILL));
+         drawList->AddPolyline(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_BORDER), true, gContext.mStyle.RotationLineThickness);
+
+         ImVec2 destinationPosOnScreen = circlePos[1];
+         char tmps[512];
+         ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle);
+         drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps);
+         drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps);
+      }
+   }
+
+   static void DrawHatchedAxis(const vec_t& axis)
+   {
+      if (gContext.mStyle.HatchedAxisLineThickness <= 0.0f)
+      {
+         return;
+      }
+
+      for (int j = 1; j < 10; j++)
+      {
+         ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP);
+         ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP);
+         gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, GetColorU32(HATCHED_AXIS_LINES), gContext.mStyle.HatchedAxisLineThickness);
+      }
+   }
+
+   static void DrawScaleGizmo(OPERATION op, int type)
+   {
+      ImDrawList* drawList = gContext.mDrawList;
+
+      if(!Intersects(op, SCALE))
+      {
+        return;
+      }
+
+      // colors
+      ImU32 colors[7];
+      ComputeColors(colors, type, SCALE);
+
+      // draw
+      vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f };
+
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID))
+      {
+         scaleDisplay = gContext.mScale;
+      }
+
+      for (int i = 0; i < 3; i++)
+      {
+         if(!Intersects(op, static_cast<OPERATION>(SCALE_X << i)))
+         {
+            continue;
+         }
+         const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i);
+         if (!gContext.mbUsing || usingAxis)
+         {
+            vec_t dirPlaneX, dirPlaneY, dirAxis;
+            bool belowAxisLimit, belowPlaneLimit;
+            ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true);
+
+            // draw axis
+            if (belowAxisLimit)
+            {
+               bool hasTranslateOnAxis = Contains(op, static_cast<OPERATION>(TRANSLATE_X << i));
+               float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f;
+               ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP);
+               ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP);
+               ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP);
+
+               if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID))
+               {
+                  ImU32 scaleLineColor = GetColorU32(SCALE_LINE);
+                  drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, scaleLineColor, gContext.mStyle.ScaleLineThickness);
+                  drawList->AddCircleFilled(worldDirSSpaceNoScale, gContext.mStyle.ScaleLineCircleSize, scaleLineColor);
+               }
+
+               if (!hasTranslateOnAxis || gContext.mbUsing)
+               {
+                  drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.ScaleLineThickness);
+               }
+               drawList->AddCircleFilled(worldDirSSpace, gContext.mStyle.ScaleLineCircleSize, colors[i + 1]);
+
+               if (gContext.mAxisFactor[i] < 0.f)
+               {
+                  DrawHatchedAxis(dirAxis * scaleDisplay[i]);
+               }
+            }
+         }
+      }
+
+      // draw screen cirle
+      drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32);
+
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type))
+      {
+         //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection);
+         ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection);
+         /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y);
+         dif.Normalize();
+         dif *= 5.f;
+         drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor);
+         drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor);
+         drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f);
+         */
+         char tmps[512];
+         //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin;
+         int componentInfoIndex = (type - MT_SCALE_X) * 3;
+         ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]);
+         drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps);
+         drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps);
+      }
+   }
+
+
+   static void DrawScaleUniveralGizmo(OPERATION op, int type)
+   {
+      ImDrawList* drawList = gContext.mDrawList;
+
+      if (!Intersects(op, SCALEU))
+      {
+         return;
+      }
+
+      // colors
+      ImU32 colors[7];
+      ComputeColors(colors, type, SCALEU);
+
+      // draw
+      vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f };
+
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID))
+      {
+         scaleDisplay = gContext.mScale;
+      }
+
+      for (int i = 0; i < 3; i++)
+      {
+         if (!Intersects(op, static_cast<OPERATION>(SCALE_XU << i)))
+         {
+            continue;
+         }
+         const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i);
+         if (!gContext.mbUsing || usingAxis)
+         {
+            vec_t dirPlaneX, dirPlaneY, dirAxis;
+            bool belowAxisLimit, belowPlaneLimit;
+            ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true);
+
+            // draw axis
+            if (belowAxisLimit)
+            {
+               bool hasTranslateOnAxis = Contains(op, static_cast<OPERATION>(TRANSLATE_X << i));
+               float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f;
+               //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal);
+               //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP);
+               ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVPLocal);
+
+#if 0
+               if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID))
+               {
+                  drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f);
+                  drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF));
+               }
+               /*
+               if (!hasTranslateOnAxis || gContext.mbUsing)
+               {
+                  drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f);
+               }
+               */
+#endif
+               drawList->AddCircleFilled(worldDirSSpace, 12.f, colors[i + 1]);
+            }
+         }
+      }
+
+      // draw screen cirle
+      drawList->AddCircle(gContext.mScreenSquareCenter, 20.f, colors[0], 32, gContext.mStyle.CenterCircleSize);
+
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type))
+      {
+         //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection);
+         ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection);
+         /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y);
+         dif.Normalize();
+         dif *= 5.f;
+         drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor);
+         drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor);
+         drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f);
+         */
+         char tmps[512];
+         //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin;
+         int componentInfoIndex = (type - MT_SCALE_X) * 3;
+         ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]);
+         drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps);
+         drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps);
+      }
+   }
+
+   static void DrawTranslationGizmo(OPERATION op, int type)
+   {
+      ImDrawList* drawList = gContext.mDrawList;
+      if (!drawList)
+      {
+         return;
+      }
+
+      if(!Intersects(op, TRANSLATE))
+      {
+         return;
+      }
+
+      // colors
+      ImU32 colors[7];
+      ComputeColors(colors, type, TRANSLATE);
+
+      const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection);
+
+      // draw
+      bool belowAxisLimit = false;
+      bool belowPlaneLimit = false;
+      for (int i = 0; i < 3; ++i)
+      {
+         vec_t dirPlaneX, dirPlaneY, dirAxis;
+         ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit);
+
+         if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_X + i))
+         {
+            // draw axis
+            if (belowAxisLimit && Intersects(op, static_cast<OPERATION>(TRANSLATE_X << i)))
+            {
+               ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP);
+               ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP);
+
+               drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.TranslationLineThickness);
+
+               // Arrow head begin
+               ImVec2 dir(origin - worldDirSSpace);
+
+               float d = sqrtf(ImLengthSqr(dir));
+               dir /= d; // Normalize
+               dir *= gContext.mStyle.TranslationLineArrowSize;
+
+               ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector
+               ImVec2 a(worldDirSSpace + dir);
+               drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]);
+               // Arrow head end
+
+               if (gContext.mAxisFactor[i] < 0.f)
+               {
+                  DrawHatchedAxis(dirAxis);
+               }
+            }
+         }
+         // draw plane
+         if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_YZ + i))
+         {
+            if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i]))
+            {
+               ImVec2 screenQuadPts[4];
+               for (int j = 0; j < 4; ++j)
+               {
+                  vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor;
+                  screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP);
+               }
+               drawList->AddPolyline(screenQuadPts, 4, GetColorU32(DIRECTION_X + i), true, 1.0f);
+               drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]);
+            }
+         }
+      }
+
+      drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32);
+
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(type))
+      {
+         ImU32 translationLineColor = GetColorU32(TRANSLATION_LINE);
+
+         ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection);
+         ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection);
+         vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f };
+         dif.Normalize();
+         dif *= 5.f;
+         drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor);
+         drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor);
+         drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f);
+
+         char tmps[512];
+         vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin;
+         int componentInfoIndex = (type - MT_MOVE_X) * 3;
+         ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]);
+         drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps);
+         drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps);
+      }
+   }
+
+   static bool CanActivate()
+   {
+      if (ImGui::IsMouseClicked(0) /*&& !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()*/)
+      {
+         return true;
+      }
+      return false;
+   }
+
+   static void HandleAndDrawLocalBounds(const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation)
+   {
+      ImGuiIO& io = ImGui::GetIO();
+      ImDrawList* drawList = gContext.mDrawList;
+
+      // compute best projection axis
+      vec_t axesWorldDirections[3];
+      vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f };
+      int axes[3];
+      unsigned int numAxes = 1;
+      axes[0] = gContext.mBoundsBestAxis;
+      int bestAxis = axes[0];
+      if (!gContext.mbUsingBounds)
+      {
+         numAxes = 0;
+         float bestDot = 0.f;
+         for (int i = 0; i < 3; i++)
+         {
+            vec_t dirPlaneNormalWorld;
+            dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource);
+            dirPlaneNormalWorld.Normalize();
+
+            float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld));
+            if (dt >= bestDot)
+            {
+               bestDot = dt;
+               bestAxis = i;
+               bestAxisWorldDirection = dirPlaneNormalWorld;
+            }
+
+            if (dt >= 0.1f)
+            {
+               axes[numAxes] = i;
+               axesWorldDirections[numAxes] = dirPlaneNormalWorld;
+               ++numAxes;
+            }
+         }
+      }
+
+      if (numAxes == 0)
+      {
+         axes[0] = bestAxis;
+         axesWorldDirections[0] = bestAxisWorldDirection;
+         numAxes = 1;
+      }
+
+      else if (bestAxis != axes[0])
+      {
+         unsigned int bestIndex = 0;
+         for (unsigned int i = 0; i < numAxes; i++)
+         {
+            if (axes[i] == bestAxis)
+            {
+               bestIndex = i;
+               break;
+            }
+         }
+         int tempAxis = axes[0];
+         axes[0] = axes[bestIndex];
+         axes[bestIndex] = tempAxis;
+         vec_t tempDirection = axesWorldDirections[0];
+         axesWorldDirections[0] = axesWorldDirections[bestIndex];
+         axesWorldDirections[bestIndex] = tempDirection;
+      }
+
+      for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex)
+      {
+         bestAxis = axes[axisIndex];
+         bestAxisWorldDirection = axesWorldDirections[axisIndex];
+
+         // corners
+         vec_t aabb[4];
+
+         int secondAxis = (bestAxis + 1) % 3;
+         int thirdAxis = (bestAxis + 2) % 3;
+
+         for (int i = 0; i < 4; i++)
+         {
+            aabb[i][3] = aabb[i][bestAxis] = 0.f;
+            aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)];
+            aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))];
+         }
+
+         // draw bounds
+         unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80);
+
+         matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection;
+         for (int i = 0; i < 4; i++)
+         {
+            ImVec2 worldBound1 = worldToPos(aabb[i], boundsMVP);
+            ImVec2 worldBound2 = worldToPos(aabb[(i + 1) % 4], boundsMVP);
+            if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2))
+            {
+               continue;
+            }
+            float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2));
+            int stepCount = (int)(boundDistance / 10.f);
+            stepCount = min(stepCount, 1000);
+            for (int j = 0; j < stepCount; j++)
+            {
+               float stepLength = 1.f / (float)stepCount;
+               float t1 = (float)j * stepLength;
+               float t2 = (float)j * stepLength + stepLength * 0.5f;
+               ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1));
+               ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2));
+               //drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f);
+               drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f);
+            }
+            vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4]) * 0.5f;
+            ImVec2 midBound = worldToPos(midPoint, boundsMVP);
+            static const float AnchorBigRadius = 8.f;
+            static const float AnchorSmallRadius = 6.f;
+            bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius);
+            bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius);
+
+            int type = MT_NONE;
+            vec_t gizmoHitProportion;
+
+            if(Intersects(operation, TRANSLATE))
+            {
+               type = GetMoveType(operation, &gizmoHitProportion);
+            }
+            if(Intersects(operation, ROTATE) && type == MT_NONE)
+            {
+               type = GetRotateType(operation);
+            }
+            if(Intersects(operation, SCALE) && type == MT_NONE)
+            {
+               type = GetScaleType(operation);
+            }
+
+            if (type != MT_NONE)
+            {
+               overBigAnchor = false;
+               overSmallAnchor = false;
+            }
+
+            ImU32 selectionColor = GetColorU32(SELECTION);
+
+            unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha);
+            unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha);
+
+            drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK);
+            drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor);
+
+            drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK);
+            drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor);
+            int oppositeIndex = (i + 2) % 4;
+            // big anchor on corners
+            if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate())
+            {
+               gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource);
+               gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource);
+               gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection);
+               gContext.mBoundsBestAxis = bestAxis;
+               gContext.mBoundsAxis[0] = secondAxis;
+               gContext.mBoundsAxis[1] = thirdAxis;
+
+               gContext.mBoundsLocalPivot.Set(0.f);
+               gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis];
+               gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis];
+
+               gContext.mbUsingBounds = true;
+               gContext.mEditingID = gContext.mActualID;
+               gContext.mBoundsMatrix = gContext.mModelSource;
+            }
+            // small anchor on middle of segment
+            if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate())
+            {
+               vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f;
+               gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource);
+               gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource);
+               gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection);
+               gContext.mBoundsBestAxis = bestAxis;
+               int indices[] = { secondAxis , thirdAxis };
+               gContext.mBoundsAxis[0] = indices[i % 2];
+               gContext.mBoundsAxis[1] = -1;
+
+               gContext.mBoundsLocalPivot.Set(0.f);
+               gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f);
+
+               gContext.mbUsingBounds = true;
+               gContext.mEditingID = gContext.mActualID;
+               gContext.mBoundsMatrix = gContext.mModelSource;
+            }
+         }
+
+         if (gContext.mbUsingBounds && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID))
+         {
+            matrix_t scale;
+            scale.SetToIdentity();
+
+            // compute projected mouse position on plan
+            const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan);
+            vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len;
+
+            // compute a reference and delta vectors base on mouse move
+            vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs();
+            vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs();
+
+            // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length
+            for (int i = 0; i < 2; i++)
+            {
+               int axisIndex1 = gContext.mBoundsAxis[i];
+               if (axisIndex1 == -1)
+               {
+                  continue;
+               }
+
+               float ratioAxis = 1.f;
+               vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs();
+
+               float dtAxis = axisDir.Dot(referenceVector);
+               float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1];
+               if (dtAxis > FLT_EPSILON)
+               {
+                  ratioAxis = axisDir.Dot(deltaVector) / dtAxis;
+               }
+
+               if (snapValues)
+               {
+                  float length = boundSize * ratioAxis;
+                  ComputeSnap(&length, snapValues[axisIndex1]);
+                  if (boundSize > FLT_EPSILON)
+                  {
+                     ratioAxis = length / boundSize;
+                  }
+               }
+               scale.component[axisIndex1] *= ratioAxis;
+            }
+
+            // transform matrix
+            matrix_t preScale, postScale;
+            preScale.Translation(-gContext.mBoundsLocalPivot);
+            postScale.Translation(gContext.mBoundsLocalPivot);
+            matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix;
+            *matrix = res;
+
+            // info text
+            char tmps[512];
+            ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection);
+            ImFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z: %.2f"
+               , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length()
+               , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length()
+               , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length()
+            );
+            drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps);
+            drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps);
+         }
+
+         if (!io.MouseDown[0]) {
+            gContext.mbUsingBounds = false;
+            gContext.mEditingID = -1;
+         }
+         if (gContext.mbUsingBounds)
+         {
+            break;
+         }
+      }
+   }
+
+   ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   //
+
+   static int GetScaleType(OPERATION op)
+   {
+      if (gContext.mbUsing)
+      {
+         return MT_NONE;
+      }
+      ImGuiIO& io = ImGui::GetIO();
+      int type = MT_NONE;
+
+      // screen
+      if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x &&
+         io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y &&
+         Contains(op, SCALE))
+      {
+         type = MT_SCALE_XYZ;
+      }
+
+      // compute
+      for (int i = 0; i < 3 && type == MT_NONE; i++)
+      {
+         if(!Intersects(op, static_cast<OPERATION>(SCALE_X << i)))
+         {
+            continue;
+         }
+         vec_t dirPlaneX, dirPlaneY, dirAxis;
+         bool belowAxisLimit, belowPlaneLimit;
+         ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true);
+         dirAxis.TransformVector(gContext.mModelLocal);
+         dirPlaneX.TransformVector(gContext.mModelLocal);
+         dirPlaneY.TransformVector(gContext.mModelLocal);
+
+         const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModelLocal.v.position, dirAxis));
+         vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len;
+
+         const float startOffset = Contains(op, static_cast<OPERATION>(TRANSLATE_X << i)) ? 1.0f : 0.1f;
+         const float endOffset = Contains(op, static_cast<OPERATION>(TRANSLATE_X << i)) ? 1.4f : 1.0f;
+         const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection);
+         const ImVec2 axisStartOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection);
+         const ImVec2 axisEndOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection);
+
+         vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen));
+
+         if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size
+         {
+            type = MT_SCALE_X + i;
+         }
+      }
+
+      // universal
+
+      vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f };
+      float dist = deltaScreen.Length();
+      if (Contains(op, SCALEU) && dist >= 17.0f && dist < 23.0f)
+      {
+         type = MT_SCALE_XYZ;
+      }
+
+      for (int i = 0; i < 3 && type == MT_NONE; i++)
+      {
+         if (!Intersects(op, static_cast<OPERATION>(SCALE_XU << i)))
+         {
+            continue;
+         }
+
+         vec_t dirPlaneX, dirPlaneY, dirAxis;
+         bool belowAxisLimit, belowPlaneLimit;
+         ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true);
+
+         // draw axis
+         if (belowAxisLimit)
+         {
+            bool hasTranslateOnAxis = Contains(op, static_cast<OPERATION>(TRANSLATE_X << i));
+            float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f;
+            //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal);
+            //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP);
+            ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale) * gContext.mScreenFactor, gContext.mMVPLocal);
+
+            float distance = sqrtf(ImLengthSqr(worldDirSSpace - io.MousePos));
+            if (distance < 12.f)
+            {
+               type = MT_SCALE_X + i;
+            }
+         }
+      }
+      return type;
+   }
+
+   static int GetRotateType(OPERATION op)
+   {
+      if (gContext.mbUsing)
+      {
+         return MT_NONE;
+      }
+      ImGuiIO& io = ImGui::GetIO();
+      int type = MT_NONE;
+
+      vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f };
+      float dist = deltaScreen.Length();
+      if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 4.0f) && dist < (gContext.mRadiusSquareCenter + 4.0f))
+      {
+         type = MT_ROTATE_SCREEN;
+      }
+
+      const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir };
+
+      vec_t modelViewPos;
+      modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat);
+
+      for (int i = 0; i < 3 && type == MT_NONE; i++)
+      {
+         if(!Intersects(op, static_cast<OPERATION>(ROTATE_X << i)))
+         {
+            continue;
+         }
+         // pickup plan
+         vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]);
+
+         const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan);
+         const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len;
+         vec_t intersectViewPos;
+         intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat);
+
+         if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON)
+         {
+            continue;
+         }
+
+         const vec_t localPos = intersectWorldPos - gContext.mModel.v.position;
+         vec_t idealPosOnCircle = Normalized(localPos);
+         idealPosOnCircle.TransformVector(gContext.mModelInverse);
+         const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP);
+
+         //gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE);
+         const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos;
+
+         const float distance = makeVect(distanceOnScreen).Length();
+         if (distance < 8.f) // pixel size
+         {
+            type = MT_ROTATE_X + i;
+         }
+      }
+
+      return type;
+   }
+
+   static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion)
+   {
+      if(!Intersects(op, TRANSLATE) || gContext.mbUsing || !gContext.mbMouseOver)
+      {
+        return MT_NONE;
+      }
+      ImGuiIO& io = ImGui::GetIO();
+      int type = MT_NONE;
+
+      // screen
+      if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x &&
+         io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y &&
+         Contains(op, TRANSLATE))
+      {
+         type = MT_MOVE_SCREEN;
+      }
+
+      const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY));
+
+      // compute
+      for (int i = 0; i < 3 && type == MT_NONE; i++)
+      {
+         vec_t dirPlaneX, dirPlaneY, dirAxis;
+         bool belowAxisLimit, belowPlaneLimit;
+         ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit);
+         dirAxis.TransformVector(gContext.mModel);
+         dirPlaneX.TransformVector(gContext.mModel);
+         dirPlaneY.TransformVector(gContext.mModel);
+
+         const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis));
+         vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len;
+
+         const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY);
+         const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY);
+
+         vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen));
+         if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast<OPERATION>(TRANSLATE_X << i))) // pixel size
+         {
+            type = MT_MOVE_X + i;
+         }
+
+         const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor));
+         const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor));
+         if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i]))
+         {
+            type = MT_MOVE_YZ + i;
+         }
+
+         if (gizmoHitProportion)
+         {
+            *gizmoHitProportion = makeVect(dx, dy, 0.f);
+         }
+      }
+      return type;
+   }
+
+   static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap)
+   {
+      if(!Intersects(op, TRANSLATE) || type != MT_NONE)
+      {
+        return false;
+      }
+      const ImGuiIO& io = ImGui::GetIO();
+      const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN;
+      bool modified = false;
+
+      // move
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation))
+      {
+#if IMGUI_VERSION_NUM >= 18723
+         ImGui::SetNextFrameWantCaptureMouse(true);
+#else
+         ImGui::CaptureMouseFromApp();
+#endif
+         const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan);
+         const float len = fabsf(signedLength); // near plan
+         const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len;
+
+         // compute delta
+         const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor;
+         vec_t delta = newOrigin - gContext.mModel.v.position;
+
+         // 1 axis constraint
+         if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z)
+         {
+            const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X;
+            const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex];
+            const float lengthOnAxis = Dot(axisValue, delta);
+            delta = axisValue * lengthOnAxis;
+         }
+
+         // snap
+         if (snap)
+         {
+            vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin;
+            if (applyRotationLocaly)
+            {
+               matrix_t modelSourceNormalized = gContext.mModelSource;
+               modelSourceNormalized.OrthoNormalize();
+               matrix_t modelSourceNormalizedInverse;
+               modelSourceNormalizedInverse.Inverse(modelSourceNormalized);
+               cumulativeDelta.TransformVector(modelSourceNormalizedInverse);
+               ComputeSnap(cumulativeDelta, snap);
+               cumulativeDelta.TransformVector(modelSourceNormalized);
+            }
+            else
+            {
+               ComputeSnap(cumulativeDelta, snap);
+            }
+            delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position;
+
+         }
+
+         if (delta != gContext.mTranslationLastDelta)
+         {
+            modified = true;
+         }
+         gContext.mTranslationLastDelta = delta;
+
+         // compute matrix & delta
+         matrix_t deltaMatrixTranslation;
+         deltaMatrixTranslation.Translation(delta);
+         if (deltaMatrix)
+         {
+            memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16);
+         }
+
+         const matrix_t res = gContext.mModelSource * deltaMatrixTranslation;
+         *(matrix_t*)matrix = res;
+
+         if (!io.MouseDown[0])
+         {
+            gContext.mbUsing = false;
+         }
+
+         type = gContext.mCurrentOperation;
+      }
+      else
+      {
+         // find new possible way to move
+         vec_t gizmoHitProportion;
+         type = GetMoveType(op, &gizmoHitProportion);
+         if (type != MT_NONE)
+         {
+#if IMGUI_VERSION_NUM >= 18723
+            ImGui::SetNextFrameWantCaptureMouse(true);
+#else
+            ImGui::CaptureMouseFromApp();
+#endif
+         }
+         if (CanActivate() && type != MT_NONE)
+         {
+            gContext.mbUsing = true;
+            gContext.mEditingID = gContext.mActualID;
+            gContext.mCurrentOperation = type;
+            vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir,
+               gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir,
+               -gContext.mCameraDir };
+
+            vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye);
+            for (unsigned int i = 0; i < 3; i++)
+            {
+               vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized);
+               movePlanNormal[i].Cross(orthoVector);
+               movePlanNormal[i].Normalize();
+            }
+            // pickup plan
+            gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]);
+            const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan);
+            gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len;
+            gContext.mMatrixOrigin = gContext.mModel.v.position;
+
+            gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor);
+         }
+      }
+      return modified;
+   }
+
+   static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap)
+   {
+      if((!Intersects(op, SCALE) && !Intersects(op, SCALEU)) || type != MT_NONE || !gContext.mbMouseOver)
+      {
+         return false;
+      }
+      ImGuiIO& io = ImGui::GetIO();
+      bool modified = false;
+
+      if (!gContext.mbUsing)
+      {
+         // find new possible way to scale
+         type = GetScaleType(op);
+         if (type != MT_NONE)
+         {
+#if IMGUI_VERSION_NUM >= 18723
+            ImGui::SetNextFrameWantCaptureMouse(true);
+#else
+            ImGui::CaptureMouseFromApp();
+#endif
+         }
+         if (CanActivate() && type != MT_NONE)
+         {
+            gContext.mbUsing = true;
+            gContext.mEditingID = gContext.mActualID;
+            gContext.mCurrentOperation = type;
+            const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir };
+            // pickup plan
+
+            gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X]);
+            const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan);
+            gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len;
+            gContext.mMatrixOrigin = gContext.mModel.v.position;
+            gContext.mScale.Set(1.f, 1.f, 1.f);
+            gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor);
+            gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length());
+            gContext.mSaveMousePosx = io.MousePos.x;
+         }
+      }
+      // scale
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation))
+      {
+#if IMGUI_VERSION_NUM >= 18723
+         ImGui::SetNextFrameWantCaptureMouse(true);
+#else
+         ImGui::CaptureMouseFromApp();
+#endif
+         const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan);
+         vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len;
+         vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor;
+         vec_t delta = newOrigin - gContext.mModelLocal.v.position;
+
+         // 1 axis constraint
+         if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z)
+         {
+            int axisIndex = gContext.mCurrentOperation - MT_SCALE_X;
+            const vec_t& axisValue = *(vec_t*)&gContext.mModelLocal.m[axisIndex];
+            float lengthOnAxis = Dot(axisValue, delta);
+            delta = axisValue * lengthOnAxis;
+
+            vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position;
+            float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector);
+
+            gContext.mScale[axisIndex] = max(ratio, 0.001f);
+         }
+         else
+         {
+            float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f;
+            gContext.mScale.Set(max(1.f + scaleDelta, 0.001f));
+         }
+
+         // snap
+         if (snap)
+         {
+            float scaleSnap[] = { snap[0], snap[0], snap[0] };
+            ComputeSnap(gContext.mScale, scaleSnap);
+         }
+
+         // no 0 allowed
+         for (int i = 0; i < 3; i++)
+            gContext.mScale[i] = max(gContext.mScale[i], 0.001f);
+
+         if (gContext.mScaleLast != gContext.mScale)
+         {
+            modified = true;
+         }
+         gContext.mScaleLast = gContext.mScale;
+
+         // compute matrix & delta
+         matrix_t deltaMatrixScale;
+         deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin);
+
+         matrix_t res = deltaMatrixScale * gContext.mModelLocal;
+         *(matrix_t*)matrix = res;
+
+         if (deltaMatrix)
+         {
+            vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin;
+
+            vec_t originalScaleDivider;
+            originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x;
+            originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y;
+            originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z;
+
+            deltaScale = deltaScale * originalScaleDivider;
+
+            deltaMatrixScale.Scale(deltaScale);
+            memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16);
+         }
+
+         if (!io.MouseDown[0])
+         {
+            gContext.mbUsing = false;
+            gContext.mScale.Set(1.f, 1.f, 1.f);
+         }
+
+         type = gContext.mCurrentOperation;
+      }
+      return modified;
+   }
+
+   static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap)
+   {
+      if(!Intersects(op, ROTATE) || type != MT_NONE || !gContext.mbMouseOver)
+      {
+        return false;
+      }
+      ImGuiIO& io = ImGui::GetIO();
+      bool applyRotationLocaly = gContext.mMode == LOCAL;
+      bool modified = false;
+
+      if (!gContext.mbUsing)
+      {
+         type = GetRotateType(op);
+
+         if (type != MT_NONE)
+         {
+#if IMGUI_VERSION_NUM >= 18723
+            ImGui::SetNextFrameWantCaptureMouse(true);
+#else
+            ImGui::CaptureMouseFromApp();
+#endif
+         }
+
+         if (type == MT_ROTATE_SCREEN)
+         {
+            applyRotationLocaly = true;
+         }
+
+         if (CanActivate() && type != MT_NONE)
+         {
+            gContext.mbUsing = true;
+            gContext.mEditingID = gContext.mActualID;
+            gContext.mCurrentOperation = type;
+            const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir };
+            // pickup plan
+            if (applyRotationLocaly)
+            {
+               gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]);
+            }
+            else
+            {
+               gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]);
+            }
+
+            const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan);
+            vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position;
+            gContext.mRotationVectorSource = Normalized(localPos);
+            gContext.mRotationAngleOrigin = ComputeAngleOnPlan();
+         }
+      }
+
+      // rotation
+      if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation))
+      {
+#if IMGUI_VERSION_NUM >= 18723
+         ImGui::SetNextFrameWantCaptureMouse(true);
+#else
+         ImGui::CaptureMouseFromApp();
+#endif
+         gContext.mRotationAngle = ComputeAngleOnPlan();
+         if (snap)
+         {
+            float snapInRadian = snap[0] * DEG2RAD;
+            ComputeSnap(&gContext.mRotationAngle, snapInRadian);
+         }
+         vec_t rotationAxisLocalSpace;
+
+         rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse);
+         rotationAxisLocalSpace.Normalize();
+
+         matrix_t deltaRotation;
+         deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin);
+         if (gContext.mRotationAngle != gContext.mRotationAngleOrigin)
+         {
+            modified = true;
+         }
+         gContext.mRotationAngleOrigin = gContext.mRotationAngle;
+
+         matrix_t scaleOrigin;
+         scaleOrigin.Scale(gContext.mModelScaleOrigin);
+
+         if (applyRotationLocaly)
+         {
+            *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModelLocal;
+         }
+         else
+         {
+            matrix_t res = gContext.mModelSource;
+            res.v.position.Set(0.f);
+
+            *(matrix_t*)matrix = res * deltaRotation;
+            ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position;
+         }
+
+         if (deltaMatrix)
+         {
+            *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel;
+         }
+
+         if (!io.MouseDown[0])
+         {
+            gContext.mbUsing = false;
+            gContext.mEditingID = -1;
+         }
+         type = gContext.mCurrentOperation;
+      }
+      return modified;
+   }
+
+   void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale)
+   {
+      matrix_t mat = *(matrix_t*)matrix;
+
+      scale[0] = mat.v.right.Length();
+      scale[1] = mat.v.up.Length();
+      scale[2] = mat.v.dir.Length();
+
+      mat.OrthoNormalize();
+
+      rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]);
+      rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2]));
+      rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]);
+
+      translation[0] = mat.v.position.x;
+      translation[1] = mat.v.position.y;
+      translation[2] = mat.v.position.z;
+   }
+
+   void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix)
+   {
+      matrix_t& mat = *(matrix_t*)matrix;
+
+      matrix_t rot[3];
+      for (int i = 0; i < 3; i++)
+      {
+         rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD);
+      }
+
+      mat = rot[0] * rot[1] * rot[2];
+
+      float validScale[3];
+      for (int i = 0; i < 3; i++)
+      {
+         if (fabsf(scale[i]) < FLT_EPSILON)
+         {
+            validScale[i] = 0.001f;
+         }
+         else
+         {
+            validScale[i] = scale[i];
+         }
+      }
+      mat.v.right *= validScale[0];
+      mat.v.up *= validScale[1];
+      mat.v.dir *= validScale[2];
+      mat.v.position.Set(translation[0], translation[1], translation[2], 1.f);
+   }
+
+   void SetID(int id)
+   {
+      gContext.mActualID = id;
+   }
+
+   void AllowAxisFlip(bool value)
+   {
+     gContext.mAllowAxisFlip = value;
+   }
+
+   void SetAxisLimit(float value)
+   {
+     gContext.mAxisLimit=value;
+   }
+
+   void SetPlaneLimit(float value)
+   {
+     gContext.mPlaneLimit = value;
+   }
+
+   bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap)
+   {
+      // Scale is always local or matrix will be skewed when applying world scale or oriented matrix
+      ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode);
+
+      // set delta to identity
+      if (deltaMatrix)
+      {
+         ((matrix_t*)deltaMatrix)->SetToIdentity();
+      }
+
+      // behind camera
+      vec_t camSpacePosition;
+      camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP);
+      if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f && !gContext.mbUsing)
+      {
+         return false;
+      }
+
+      // --
+      int type = MT_NONE;
+      bool manipulated = false;
+      if (gContext.mbEnable)
+      {
+         if (!gContext.mbUsingBounds)
+         {
+            manipulated = HandleTranslation(matrix, deltaMatrix, operation, type, snap) ||
+                          HandleScale(matrix, deltaMatrix, operation, type, snap) ||
+                          HandleRotation(matrix, deltaMatrix, operation, type, snap);
+         }
+      }
+
+      if (localBounds && !gContext.mbUsing)
+      {
+         HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation);
+      }
+
+      gContext.mOperation = operation;
+      if (!gContext.mbUsingBounds)
+      {
+         DrawRotationGizmo(operation, type);
+         DrawTranslationGizmo(operation, type);
+         DrawScaleGizmo(operation, type);
+         DrawScaleUniveralGizmo(operation, type);
+      }
+      return manipulated;
+   }
+
+   void SetGizmoSizeClipSpace(float value)
+   {
+      gContext.mGizmoSizeClipSpace = value;
+   }
+
+   ///////////////////////////////////////////////////////////////////////////////////////////////////
+   void ComputeFrustumPlanes(vec_t* frustum, const float* clip)
+   {
+      frustum[0].x = clip[3] - clip[0];
+      frustum[0].y = clip[7] - clip[4];
+      frustum[0].z = clip[11] - clip[8];
+      frustum[0].w = clip[15] - clip[12];
+
+      frustum[1].x = clip[3] + clip[0];
+      frustum[1].y = clip[7] + clip[4];
+      frustum[1].z = clip[11] + clip[8];
+      frustum[1].w = clip[15] + clip[12];
+
+      frustum[2].x = clip[3] + clip[1];
+      frustum[2].y = clip[7] + clip[5];
+      frustum[2].z = clip[11] + clip[9];
+      frustum[2].w = clip[15] + clip[13];
+
+      frustum[3].x = clip[3] - clip[1];
+      frustum[3].y = clip[7] - clip[5];
+      frustum[3].z = clip[11] - clip[9];
+      frustum[3].w = clip[15] - clip[13];
+
+      frustum[4].x = clip[3] - clip[2];
+      frustum[4].y = clip[7] - clip[6];
+      frustum[4].z = clip[11] - clip[10];
+      frustum[4].w = clip[15] - clip[14];
+
+      frustum[5].x = clip[3] + clip[2];
+      frustum[5].y = clip[7] + clip[6];
+      frustum[5].z = clip[11] + clip[10];
+      frustum[5].w = clip[15] + clip[14];
+
+      for (int i = 0; i < 6; i++)
+      {
+         frustum[i].Normalize();
+      }
+   }
+
+   void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount)
+   {
+      matrix_t viewInverse;
+      viewInverse.Inverse(*(matrix_t*)view);
+
+      struct CubeFace
+      {
+         float z;
+         ImVec2 faceCoordsScreen[4];
+         ImU32 color;
+      };
+      CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6);
+
+      if (!faces)
+      {
+         return;
+      }
+
+      vec_t frustum[6];
+      matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection;
+      ComputeFrustumPlanes(frustum, viewProjection.m16);
+
+      int cubeFaceCount = 0;
+      for (int cube = 0; cube < matrixCount; cube++)
+      {
+         const float* matrix = &matrices[cube * 16];
+
+         matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection;
+
+         for (int iFace = 0; iFace < 6; iFace++)
+         {
+            const int normalIndex = (iFace % 3);
+            const int perpXIndex = (normalIndex + 1) % 3;
+            const int perpYIndex = (normalIndex + 2) % 3;
+            const float invert = (iFace > 2) ? -1.f : 1.f;
+
+            const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex],
+               directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex],
+               directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex],
+               directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex],
+            };
+
+            // clipping
+            /*
+            bool skipFace = false;
+            for (unsigned int iCoord = 0; iCoord < 4; iCoord++)
+            {
+               vec_t camSpacePosition;
+               camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res);
+               if (camSpacePosition.z < 0.001f)
+               {
+                  skipFace = true;
+                  break;
+               }
+            }
+            if (skipFace)
+            {
+               continue;
+            }
+            */
+            vec_t centerPosition, centerPositionVP;
+            centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix);
+            centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res);
+
+            bool inFrustum = true;
+            for (int iFrustum = 0; iFrustum < 6; iFrustum++)
+            {
+               float dist = DistanceToPlane(centerPosition, frustum[iFrustum]);
+               if (dist < 0.f)
+               {
+                  inFrustum = false;
+                  break;
+               }
+            }
+
+            if (!inFrustum)
+            {
+               continue;
+            }
+            CubeFace& cubeFace = faces[cubeFaceCount];
+
+            // 3D->2D
+            //ImVec2 faceCoordsScreen[4];
+            for (unsigned int iCoord = 0; iCoord < 4; iCoord++)
+            {
+               cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res);
+            }
+
+            ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex);
+            cubeFace.color = directionColor | IM_COL32(0x80, 0x80, 0x80, 0);
+
+            cubeFace.z = centerPositionVP.z / centerPositionVP.w;
+            cubeFaceCount++;
+         }
+      }
+      qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) {
+         CubeFace* a = (CubeFace*)_a;
+         CubeFace* b = (CubeFace*)_b;
+         if (a->z < b->z)
+         {
+            return 1;
+         }
+         return -1;
+         });
+      // draw face with lighter color
+      for (int iFace = 0; iFace < cubeFaceCount; iFace++)
+      {
+         const CubeFace& cubeFace = faces[iFace];
+         gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color);
+      }
+
+      _freea(faces);
+   }
+
+   void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize)
+   {
+      matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection;
+      vec_t frustum[6];
+      ComputeFrustumPlanes(frustum, viewProjection.m16);
+      matrix_t res = *(matrix_t*)matrix * viewProjection;
+
+      for (float f = -gridSize; f <= gridSize; f += 1.f)
+      {
+         for (int dir = 0; dir < 2; dir++)
+         {
+            vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize);
+            vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize);
+            bool visible = true;
+            for (int i = 0; i < 6; i++)
+            {
+               float dA = DistanceToPlane(ptA, frustum[i]);
+               float dB = DistanceToPlane(ptB, frustum[i]);
+               if (dA < 0.f && dB < 0.f)
+               {
+                  visible = false;
+                  break;
+               }
+               if (dA > 0.f && dB > 0.f)
+               {
+                  continue;
+               }
+               if (dA < 0.f)
+               {
+                  float len = fabsf(dA - dB);
+                  float t = fabsf(dA) / len;
+                  ptA.Lerp(ptB, t);
+               }
+               if (dB < 0.f)
+               {
+                  float len = fabsf(dB - dA);
+                  float t = fabsf(dB) / len;
+                  ptB.Lerp(ptA, t);
+               }
+            }
+            if (visible)
+            {
+               ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF);
+               col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col;
+               col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF): col;
+
+               float thickness = 1.f;
+               thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness;
+               thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness;
+
+               gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness);
+            }
+         }
+      }
+   }
+
+   void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor)
+   {
+      // Scale is always local or matrix will be skewed when applying world scale or oriented matrix
+      ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode);
+      ViewManipulate(view, length, position, size, backgroundColor);
+   }
+
+   void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor)
+   {
+      static bool isDraging = false;
+      static bool isClicking = false;
+      static bool isInside = false;
+      static vec_t interpolationUp;
+      static vec_t interpolationDir;
+      static int interpolationFrames = 0;
+      const vec_t referenceUp = makeVect(0.f, 1.f, 0.f);
+
+      matrix_t svgView, svgProjection;
+      svgView = gContext.mViewMat;
+      svgProjection = gContext.mProjectionMat;
+
+      ImGuiIO& io = ImGui::GetIO();
+      gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor);
+      matrix_t viewInverse;
+      viewInverse.Inverse(*(matrix_t*)view);
+
+      const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length;
+
+      // view/projection matrices
+      const float distance = 3.f;
+      matrix_t cubeProjection, cubeView;
+      float fov = acosf(distance / (sqrtf(distance * distance + 3.f))) * RAD2DEG;
+      Perspective(fov / sqrtf(2.f), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16);
+
+      vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]);
+      vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]);
+      vec_t eye = dir * distance;
+      vec_t zero = makeVect(0.f, 0.f);
+      LookAt(&eye.x, &zero.x, &up.x, cubeView.m16);
+
+      // set context
+      gContext.mViewMat = cubeView;
+      gContext.mProjectionMat = cubeProjection;
+      ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size);
+
+      const matrix_t res = cubeView * cubeProjection;
+
+      // panels
+      static const ImVec2 panelPosition[9] = { ImVec2(0.75f,0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f),
+         ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f),
+         ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) };
+
+      static const ImVec2 panelSize[9] = { ImVec2(0.25f,0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f),
+         ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f),
+         ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) };
+
+      // tag faces
+      bool boxes[27]{};
+      static int overBox = -1;
+      for (int iPass = 0; iPass < 2; iPass++)
+      {
+         for (int iFace = 0; iFace < 6; iFace++)
+         {
+            const int normalIndex = (iFace % 3);
+            const int perpXIndex = (normalIndex + 1) % 3;
+            const int perpYIndex = (normalIndex + 2) % 3;
+            const float invert = (iFace > 2) ? -1.f : 1.f;
+            const vec_t indexVectorX = directionUnary[perpXIndex] * invert;
+            const vec_t indexVectorY = directionUnary[perpYIndex] * invert;
+            const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY;
+
+            // plan local space
+            const vec_t n = directionUnary[normalIndex] * invert;
+            vec_t viewSpaceNormal = n;
+            vec_t viewSpacePoint = n * 0.5f;
+            viewSpaceNormal.TransformVector(cubeView);
+            viewSpaceNormal.Normalize();
+            viewSpacePoint.TransformPoint(cubeView);
+            const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal);
+
+            // back face culling
+            if (viewSpaceFacePlan.w > 0.f)
+            {
+               continue;
+            }
+
+            const vec_t facePlan = BuildPlan(n * 0.5f, n);
+
+            const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan);
+            vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f);
+
+            float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f;
+            float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f;
+
+            // panels
+            const vec_t dx = directionUnary[perpXIndex];
+            const vec_t dy = directionUnary[perpYIndex];
+            const vec_t origin = directionUnary[normalIndex] - dx - dy;
+            for (int iPanel = 0; iPanel < 9; iPanel++)
+            {
+               vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f);
+               const ImVec2 p = panelPosition[iPanel] * 2.f;
+               const ImVec2 s = panelSize[iPanel] * 2.f;
+               ImVec2 faceCoordsScreen[4];
+               vec_t panelPos[4] = { dx * p.x + dy * p.y,
+                                     dx * p.x + dy * (p.y + s.y),
+                                     dx * (p.x + s.x) + dy * (p.y + s.y),
+                                     dx * (p.x + s.x) + dy * p.y };
+
+               for (unsigned int iCoord = 0; iCoord < 4; iCoord++)
+               {
+                  faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size);
+               }
+
+               const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] };
+               bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y;
+               int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z);
+               IM_ASSERT(boxCoordInt < 27);
+               boxes[boxCoordInt] |= insidePanel && (!isDraging) && gContext.mbMouseOver;
+
+               // draw face with lighter color
+               if (iPass)
+               {
+                  ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex);
+                  gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor | IM_COL32(0x80, 0x80, 0x80, 0x80)) | (isInside ? IM_COL32(0x08, 0x08, 0x08, 0) : 0));
+                  if (boxes[boxCoordInt])
+                  {
+                     gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, IM_COL32(0xF0, 0xA0, 0x60, 0x80));
+
+                     if (io.MouseDown[0] && !isClicking && !isDraging && GImGui->ActiveId == 0) {
+                        overBox = boxCoordInt;
+                        isClicking = true;
+                        isDraging = true;
+                     }
+                  }
+               }
+            }
+         }
+      }
+      if (interpolationFrames)
+      {
+         interpolationFrames--;
+         vec_t newDir = viewInverse.v.dir;
+         newDir.Lerp(interpolationDir, 0.2f);
+         newDir.Normalize();
+
+         vec_t newUp = viewInverse.v.up;
+         newUp.Lerp(interpolationUp, 0.3f);
+         newUp.Normalize();
+         newUp = interpolationUp;
+         vec_t newEye = camTarget + newDir * length;
+         LookAt(&newEye.x, &camTarget.x, &newUp.x, view);
+      }
+      isInside = gContext.mbMouseOver && ImRect(position, position + size).Contains(io.MousePos);
+
+      if (io.MouseDown[0] && (fabsf(io.MouseDelta[0]) || fabsf(io.MouseDelta[1])) && isClicking)
+      {
+         isClicking = false;
+      }
+
+      if (!io.MouseDown[0])
+      {
+         if (isClicking)
+         {
+            // apply new view direction
+            int cx = overBox / 9;
+            int cy = (overBox - cx * 9) / 3;
+            int cz = overBox % 3;
+            interpolationDir = makeVect(1.f - (float)cx, 1.f - (float)cy, 1.f - (float)cz);
+            interpolationDir.Normalize();
+
+            if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f)
+            {
+               vec_t right = viewInverse.v.right;
+               if (fabsf(right.x) > fabsf(right.z))
+               {
+                  right.z = 0.f;
+               }
+               else
+               {
+                  right.x = 0.f;
+               }
+               right.Normalize();
+               interpolationUp = Cross(interpolationDir, right);
+               interpolationUp.Normalize();
+            }
+            else
+            {
+               interpolationUp = referenceUp;
+            }
+            interpolationFrames = 40;
+
+         }
+         isClicking = false;
+         isDraging = false;
+      }
+
+
+      if (isDraging)
+      {
+         matrix_t rx, ry, roll;
+
+         rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f);
+         ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f);
+
+         roll = rx * ry;
+
+         vec_t newDir = viewInverse.v.dir;
+         newDir.TransformVector(roll);
+         newDir.Normalize();
+
+         // clamp
+         vec_t planDir = Cross(viewInverse.v.right, referenceUp);
+         planDir.y = 0.f;
+         planDir.Normalize();
+         float dt = Dot(planDir, newDir);
+         if (dt < 0.0f)
+         {
+            newDir += planDir * dt;
+            newDir.Normalize();
+         }
+
+         vec_t newEye = camTarget + newDir * length;
+         LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view);
+      }
+
+      // restore view/projection because it was used to compute ray
+      ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode);
+   }
+};

+ 272 - 0
Dependencies/include/ImGuizmo/ImGuizmo.h

@@ -0,0 +1,272 @@
+// https://github.com/CedricGuillemet/ImGuizmo
+// v 1.89 WIP
+//
+// The MIT License(MIT)
+//
+// Copyright(c) 2021 Cedric Guillemet
+//
+// 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.
+//
+// -------------------------------------------------------------------------------------------
+// History :
+// 2019/11/03 View gizmo
+// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode.
+// 2016/09/09 Hatched negative axis. Snapping. Documentation update.
+// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved
+// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results.
+// 2016/08/31 First version
+//
+// -------------------------------------------------------------------------------------------
+// Future (no order):
+//
+// - Multi view
+// - display rotation/translation/scale infos in local/world space and not only local
+// - finish local/world matrix application
+// - OPERATION as bitmask
+// 
+// -------------------------------------------------------------------------------------------
+// Example 
+#if 0
+void EditTransform(const Camera& camera, matrix_t& matrix)
+{
+   static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE);
+   static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD);
+   if (ImGui::IsKeyPressed(90))
+      mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
+   if (ImGui::IsKeyPressed(69))
+      mCurrentGizmoOperation = ImGuizmo::ROTATE;
+   if (ImGui::IsKeyPressed(82)) // r Key
+      mCurrentGizmoOperation = ImGuizmo::SCALE;
+   if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE))
+      mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
+   ImGui::SameLine();
+   if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE))
+      mCurrentGizmoOperation = ImGuizmo::ROTATE;
+   ImGui::SameLine();
+   if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE))
+      mCurrentGizmoOperation = ImGuizmo::SCALE;
+   float matrixTranslation[3], matrixRotation[3], matrixScale[3];
+   ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale);
+   ImGui::InputFloat3("Tr", matrixTranslation, 3);
+   ImGui::InputFloat3("Rt", matrixRotation, 3);
+   ImGui::InputFloat3("Sc", matrixScale, 3);
+   ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16);
+
+   if (mCurrentGizmoOperation != ImGuizmo::SCALE)
+   {
+      if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL))
+         mCurrentGizmoMode = ImGuizmo::LOCAL;
+      ImGui::SameLine();
+      if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD))
+         mCurrentGizmoMode = ImGuizmo::WORLD;
+   }
+   static bool useSnap(false);
+   if (ImGui::IsKeyPressed(83))
+      useSnap = !useSnap;
+   ImGui::Checkbox("", &useSnap);
+   ImGui::SameLine();
+   vec_t snap;
+   switch (mCurrentGizmoOperation)
+   {
+   case ImGuizmo::TRANSLATE:
+      snap = config.mSnapTranslation;
+      ImGui::InputFloat3("Snap", &snap.x);
+      break;
+   case ImGuizmo::ROTATE:
+      snap = config.mSnapRotation;
+      ImGui::InputFloat("Angle Snap", &snap.x);
+      break;
+   case ImGuizmo::SCALE:
+      snap = config.mSnapScale;
+      ImGui::InputFloat("Scale Snap", &snap.x);
+      break;
+   }
+   ImGuiIO& io = ImGui::GetIO();
+   ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);
+   ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL);
+}
+#endif
+#pragma once
+
+#ifdef USE_IMGUI_API
+#include "imconfig.h"
+#endif
+#ifndef IMGUI_API
+#define IMGUI_API
+#endif
+
+#ifndef IMGUIZMO_NAMESPACE
+#define IMGUIZMO_NAMESPACE ImGuizmo
+#endif
+
+namespace IMGUIZMO_NAMESPACE
+{
+   // call inside your own window and before Manipulate() in order to draw gizmo to that window.
+   // Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()).
+   IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr);
+
+   // call BeginFrame right after ImGui_XXXX_NewFrame();
+   IMGUI_API void BeginFrame();
+
+   // this is necessary because when imguizmo is compiled into a dll, and imgui into another
+   // globals are not shared between them.
+   // More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam
+   // expose method to set imgui context
+   IMGUI_API void SetImGuiContext(ImGuiContext* ctx);
+
+   // return true if mouse cursor is over any gizmo control (axis, plan or screen component)
+   IMGUI_API bool IsOver();
+
+   // return true if mouse IsOver or if the gizmo is in moving state
+   IMGUI_API bool IsUsing();
+
+   // return true if any gizmo is in moving state
+   IMGUI_API bool IsUsingAny();
+
+   // enable/disable the gizmo. Stay in the state until next call to Enable.
+   // gizmo is rendered with gray half transparent color when disabled
+   IMGUI_API void Enable(bool enable);
+
+   // helper functions for manualy editing translation/rotation/scale with an input float
+   // translation, rotation and scale float points to 3 floats each
+   // Angles are in degrees (more suitable for human editing)
+   // example:
+   // float matrixTranslation[3], matrixRotation[3], matrixScale[3];
+   // ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale);
+   // ImGui::InputFloat3("Tr", matrixTranslation, 3);
+   // ImGui::InputFloat3("Rt", matrixRotation, 3);
+   // ImGui::InputFloat3("Sc", matrixScale, 3);
+   // ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16);
+   //
+   // These functions have some numerical stability issues for now. Use with caution.
+   IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale);
+   IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix);
+
+   IMGUI_API void SetRect(float x, float y, float width, float height);
+   // default is false
+   IMGUI_API void SetOrthographic(bool isOrthographic);
+
+   // Render a cube with face color corresponding to face normal. Usefull for debug/tests
+   IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount);
+   IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize);
+
+   // call it when you want a gizmo
+   // Needs view and projection matrices. 
+   // matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional
+   // translation is applied in world space
+   enum OPERATION
+   {
+      TRANSLATE_X      = (1u << 0),
+      TRANSLATE_Y      = (1u << 1),
+      TRANSLATE_Z      = (1u << 2),
+      ROTATE_X         = (1u << 3),
+      ROTATE_Y         = (1u << 4),
+      ROTATE_Z         = (1u << 5),
+      ROTATE_SCREEN    = (1u << 6),
+      SCALE_X          = (1u << 7),
+      SCALE_Y          = (1u << 8),
+      SCALE_Z          = (1u << 9),
+      BOUNDS           = (1u << 10),
+      SCALE_XU         = (1u << 11),
+      SCALE_YU         = (1u << 12),
+      SCALE_ZU         = (1u << 13),
+
+      TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z,
+      ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN,
+      SCALE = SCALE_X | SCALE_Y | SCALE_Z,
+      SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal
+      UNIVERSAL = TRANSLATE | ROTATE | SCALEU
+   };
+
+   inline OPERATION operator|(OPERATION lhs, OPERATION rhs)
+   {
+     return static_cast<OPERATION>(static_cast<int>(lhs) | static_cast<int>(rhs));
+   }
+
+   enum MODE
+   {
+      LOCAL,
+      WORLD
+   };
+
+   IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL);
+   //
+   // Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en
+   // It seems to be a defensive patent in the US. I don't think it will bring troubles using it as
+   // other software are using the same mechanics. But just in case, you are now warned!
+   //
+   IMGUI_API void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor);
+
+   // use this version if you did not call Manipulate before and you are just using ViewManipulate
+   IMGUI_API void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor);
+
+   IMGUI_API void SetID(int id);
+
+   // return true if the cursor is over the operation's gizmo
+   IMGUI_API bool IsOver(OPERATION op);
+   IMGUI_API void SetGizmoSizeClipSpace(float value);
+
+   // Allow axis to flip
+   // When true (default), the guizmo axis flip for better visibility
+   // When false, they always stay along the positive world/local axis
+   IMGUI_API void AllowAxisFlip(bool value);
+
+   // Configure the limit where axis are hidden
+   IMGUI_API void SetAxisLimit(float value);
+   // Configure the limit where planes are hiden
+   IMGUI_API void SetPlaneLimit(float value);
+
+   enum COLOR
+   {
+      DIRECTION_X,      // directionColor[0]
+      DIRECTION_Y,      // directionColor[1]
+      DIRECTION_Z,      // directionColor[2]
+      PLANE_X,          // planeColor[0]
+      PLANE_Y,          // planeColor[1]
+      PLANE_Z,          // planeColor[2]
+      SELECTION,        // selectionColor
+      INACTIVE,         // inactiveColor
+      TRANSLATION_LINE, // translationLineColor
+      SCALE_LINE,
+      ROTATION_USING_BORDER,
+      ROTATION_USING_FILL,
+      HATCHED_AXIS_LINES,
+      TEXT,
+      TEXT_SHADOW,
+      COUNT
+   };
+
+   struct Style
+   {
+      IMGUI_API Style();
+
+      float TranslationLineThickness;   // Thickness of lines for translation gizmo
+      float TranslationLineArrowSize;   // Size of arrow at the end of lines for translation gizmo
+      float RotationLineThickness;      // Thickness of lines for rotation gizmo
+      float RotationOuterLineThickness; // Thickness of line surrounding the rotation gizmo
+      float ScaleLineThickness;         // Thickness of lines for scale gizmo
+      float ScaleLineCircleSize;        // Size of circle at the end of lines for scale gizmo
+      float HatchedAxisLineThickness;   // Thickness of hatched axis lines
+      float CenterCircleSize;           // Size of circle at the center of the translate/scale gizmo
+
+      ImVec4 Colors[COLOR::COUNT];
+   };
+
+   IMGUI_API Style& GetStyle();
+}

+ 7 - 1
Praxis3D/Data/Scripts/Camera_free_object_spawn.lua

@@ -74,6 +74,12 @@ function update (p_deltaTime)
 		-- Rotate camera left/right on a fixed Y direction (up/down) to not introduce any roll
 		localTransformMat4 = localTransformMat4:rotate(toRadianF(horizontalAngleF), Vec3.new(0.0, 1.0, 0.0))
 	end
+	
+	if mouseRightKey:isActivated() then
+		setMouseCapture(true)
+	else
+		setMouseCapture(false)
+	end
 		
 	-- Get the view direction that is facing forward
 	forwardDirectionVec3 = localTransformInverseMat4:getRotZVec3()
@@ -133,7 +139,7 @@ function update (p_deltaTime)
 	
 	-- Set the new position of the camera, and keep the W variable the same
 	localTransformMat4:setPosVec4(Vec4.new(positionVec3, localTransformMat4:getPosVec4().w))
-	
+		
 	-- Update the camera with the new matrix
 	spatialData:setLocalTransform(localTransformMat4)
 end

+ 4 - 2
Praxis3D/Praxis3D.vcxproj

@@ -68,7 +68,7 @@
     <OutDir>$(SolutionDir)\Builds\$(Platform)\$(Configuration)\</OutDir>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
-    <IncludePath>$(SolutionDir)Dependencies\include\;$(SolutionDir)Dependencies\include\Lua\;$(SolutionDir)Dependencies\include\imgui\;$(SolutionDir)Dependencies\include\sdl\;$(SolutionDir)Dependencies\include\bullet3\;$(SolutionDir)Dependencies\include\imgui_tex_inspect\;$(IncludePath)</IncludePath>
+    <IncludePath>$(SolutionDir)Dependencies\include\;$(SolutionDir)Dependencies\include\Lua\;$(SolutionDir)Dependencies\include\imgui\;$(SolutionDir)Dependencies\include\sdl\;$(SolutionDir)Dependencies\include\bullet3\;$(SolutionDir)Dependencies\include\imgui_tex_inspect\;$(SolutionDir)Dependencies\include\ImGuizmo\;$(IncludePath)</IncludePath>
     <LibraryPath>$(SolutionDir)Dependencies\$(Platform)\$(Configuration)\lib\;$(LibraryPath)</LibraryPath>
     <OutDir>$(SolutionDir)Builds\$(Platform)\$(Configuration)\</OutDir>
   </PropertyGroup>
@@ -116,7 +116,7 @@
       <WarningLevel>Level3</WarningLevel>
       <SDLCheck>true</SDLCheck>
       <Optimization>Disabled</Optimization>
-      <PreprocessorDefinitions>NOMINMAX;_DEBUG;_CONSOLE;_CRT_SECURE_NO_DEPRECATE;CUSTOM_IMGUIFILEDIALOG_CONFIG="..\\..\\..\\Praxis3D\Source\EngineDefinitions.h";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>NOMINMAX;_DEBUG;_CONSOLE;_CRT_SECURE_NO_DEPRECATE;CUSTOM_IMGUIFILEDIALOG_CONFIG="..\\..\\..\\Praxis3D\Source\EngineDefinitions.h";GLM_FORCE_CTOR_INIT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <ConformanceMode>false</ConformanceMode>
@@ -130,6 +130,7 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\Dependencies\include\ImGuiFileDialog\ImGuiFileDialog.cpp" />
+    <ClCompile Include="..\Dependencies\include\ImGuizmo\ImGuizmo.cpp" />
     <ClCompile Include="..\Dependencies\include\imgui\backends\imgui_impl_opengl3.cpp" />
     <ClCompile Include="..\Dependencies\include\imgui\backends\imgui_impl_sdl.cpp" />
     <ClCompile Include="..\Dependencies\include\imgui\imgui.cpp" />
@@ -212,6 +213,7 @@
     <ClCompile Include="Source\WorldTask.cpp" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="..\Dependencies\include\ImGuizmo\ImGuizmo.h" />
     <ClInclude Include="..\Dependencies\include\imgui\backends\imgui_impl_opengl3.h" />
     <ClInclude Include="..\Dependencies\include\imgui\backends\imgui_impl_opengl3_loader.h" />
     <ClInclude Include="..\Dependencies\include\imgui\backends\imgui_impl_sdl.h" />

+ 9 - 0
Praxis3D/Praxis3D.vcxproj.filters

@@ -202,6 +202,9 @@
     <Filter Include="3rd Party\ImGuiFileDialog">
       <UniqueIdentifier>{64b58db8-35ed-4a66-b8c7-bab40e593fb2}</UniqueIdentifier>
     </Filter>
+    <Filter Include="3rd Party\ImGuizmo">
+      <UniqueIdentifier>{35332447-1f2e-4c5e-84ae-3e83c20e317d}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="main.cpp">
@@ -447,6 +450,9 @@
     <ClCompile Include="Source\AudioSystem.cpp">
       <Filter>Audio\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Dependencies\include\ImGuizmo\ImGuizmo.cpp">
+      <Filter>3rd Party\ImGuizmo</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Source\ErrorCodes.h">
@@ -929,6 +935,9 @@
     <ClInclude Include="..\Dependencies\include\imgui\imgui_stdlib.h">
       <Filter>3rd Party\imgui\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\Dependencies\include\ImGuizmo\ImGuizmo.h">
+      <Filter>3rd Party\ImGuizmo</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="Data\config.ini" />

+ 83 - 0
Praxis3D/Source/AudioScene.cpp

@@ -442,6 +442,24 @@ void AudioScene::loadInBackground()
 {
 }
 
+std::vector<SystemObject *> AudioScene::getComponents(const EntityID p_entityID)
+{
+	std::vector<SystemObject *> returnVector;
+
+	// Get the entity registry 
+	auto &entityRegistry = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World))->getEntityRegistry();
+
+	auto *soundComponent = entityRegistry.try_get<SoundComponent>(p_entityID);
+	if(soundComponent != nullptr)
+		returnVector.push_back(soundComponent);
+
+	auto *soundListenerComponent = entityRegistry.try_get<SoundListenerComponent>(p_entityID);
+	if(soundListenerComponent != nullptr)
+		returnVector.push_back(soundListenerComponent);
+
+	return returnVector;
+}
+
 std::vector<SystemObject*> AudioScene::createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading)
 {
 	return createComponents(p_entityID, p_constructionInfo.m_audioComponents, p_startLoading);
@@ -590,6 +608,71 @@ void AudioScene::changeOccurred(ObservedSubject *p_subject, BitMask p_changeType
 	}
 }
 
+void AudioScene::receiveData(const DataType p_dataType, void *p_data, const bool p_deleteAfterReceiving)
+{
+	switch(p_dataType)
+	{
+		case DataType::DataType_DeleteComponent:
+			{
+				// Get the world scene required for getting the entity registry and deleting components
+				WorldScene *worldScene = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World));
+
+				// Get the entity registry 
+				auto &entityRegistry = worldScene->getEntityRegistry();
+
+				// Get entity and component data
+				auto const *componentData = static_cast<EntityAndComponent *>(p_data);
+
+				// Delete the component based on its type
+				switch(componentData->m_componentType)
+				{
+					case ComponentType::ComponentType_SoundComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<SoundComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// If component is active and sound is playing, stop the sound
+								if(component->isObjectActive())
+								{
+									if(component->m_playing)
+									{
+										component->m_channel->stop();
+										component->m_playing = false;
+									}
+								}
+
+								// If sound exists, release its memory
+								if(component->m_sound != nullptr)
+									component->m_sound->release();
+
+								// Delete component
+								worldScene->removeComponent<SoundComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+
+					case ComponentType::ComponentType_SoundListenerComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<SoundListenerComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								worldScene->removeComponent<SoundListenerComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+				}
+
+				// Delete the sent data if the ownership of it was transfered
+				if(p_deleteAfterReceiving)
+					delete componentData;
+			}
+			break;
+	}
+}
+
 void AudioScene::createSound(SoundComponent &p_soundComponent)
 {
 	if(p_soundComponent.m_sound != nullptr)

+ 6 - 1
Praxis3D/Source/AudioScene.h

@@ -71,6 +71,9 @@ public:
 
 	void loadInBackground();
 
+	// Get all the created components of the given entity that belong to this scene
+	std::vector<SystemObject *> getComponents(const EntityID p_entityID);
+
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true);
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const AudioComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true)
 	{
@@ -115,9 +118,11 @@ public:
 
 	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType);
 
+	void receiveData(const DataType p_dataType, void *p_data, const bool p_deleteAfterReceiving);
+
 	SystemTask *getSystemTask() { return m_audioTask; };
 	Systems::TypeID getSystemType() { return Systems::TypeID::Audio; };
-	BitMask getDesiredSystemChanges() { return Systems::Changes::Generic::CreateObject || Systems::Changes::Generic::DeleteObject || Systems::Changes::Audio::AllVolume; }
+	BitMask getDesiredSystemChanges() { return Systems::Changes::Generic::CreateObject | Systems::Changes::Generic::DeleteObject | Systems::Changes::Audio::AllVolume; }
 	BitMask getPotentialSystemChanges() { return Systems::Changes::None; }
 	const std::vector<std::pair<std::string, FMOD::Studio::Bank *>> &getBankFilenames() const { return m_bankFilenames; }
 	const float getVolume(const AudioBusType p_audioBusType) const { return m_volume[p_audioBusType]; }

+ 1 - 0
Praxis3D/Source/CommonDefinitions.cpp

@@ -1,4 +1,5 @@
 #include "CommonDefinitions.h"
 
+DEFINE_ENUM(ComponentType, COMPONENT_TYPE)
 DEFINE_ENUM(RenderPassType, RENDER_PASS_TYPE)
 DEFINE_ENUM(ObjectMaterialType, OBJ_MATERIAL_ID)

+ 21 - 0
Praxis3D/Source/CommonDefinitions.h

@@ -11,6 +11,27 @@ typedef std::vector<std::function<void()>> Functors;
 
 constexpr EntityID NULL_ENTITY_ID = std::numeric_limits<EntityID>::max();
 
+#define COMPONENT_TYPE(Code) \
+	/* Audio components */ \
+	Code(ComponentType_SoundComponent, = 0) \
+	Code(ComponentType_SoundListenerComponent, ) \
+	/* Graphics components */ \
+	Code(ComponentType_CameraComponent, ) \
+	Code(ComponentType_LightComponent, ) \
+	Code(ComponentType_ModelComponent, ) \
+	Code(ComponentType_ShaderComponent, ) \
+	/* GUI components */ \
+	Code(ComponentType_GUISequenceComponent, ) \
+	/* Physics components */ \
+	Code(ComponentType_RigidBodyComponent, ) \
+	/* Scripting components */ \
+	Code(ComponentType_LuaComponent, ) \
+	/* World components */ \
+	Code(ComponentType_ObjectMaterialComponent, ) \
+	Code(ComponentType_SpatialComponent, ) \
+	Code(ComponentType_NumOfTypes, )
+DECLARE_ENUM(ComponentType, COMPONENT_TYPE)
+
 enum EngineChangeType : unsigned int
 {
 	EngineChangeType_None = 0,

+ 3 - 0
Praxis3D/Source/Config.cpp

@@ -204,6 +204,7 @@ void Config::init()
 	AddVariablePredef(m_GUIVar, editor_asset_texture_button_size_y);
 	AddVariablePredef(m_GUIVar, editor_audio_banks_max_height);
 	AddVariablePredef(m_GUIVar, editor_float_slider_speed);
+	AddVariablePredef(m_GUIVar, editor_inspector_button_width_multiplier);
 	AddVariablePredef(m_GUIVar, editor_lua_variables_max_height);
 	AddVariablePredef(m_GUIVar, editor_play_button_size);
 	AddVariablePredef(m_GUIVar, editor_render_pass_max_height);
@@ -218,6 +219,8 @@ void Config::init()
 	AddVariablePredef(m_GUIVar, editor_button_arrow_up_texture);
 	AddVariablePredef(m_GUIVar, editor_button_delete_entry_texture); 
 	AddVariablePredef(m_GUIVar, editor_button_gui_sequence_texture);
+	AddVariablePredef(m_GUIVar, editor_button_guizmo_rotate_texture);
+	AddVariablePredef(m_GUIVar, editor_button_guizmo_translate_texture);
 	AddVariablePredef(m_GUIVar, editor_button_open_file_texture);
 	AddVariablePredef(m_GUIVar, editor_button_open_asset_list_texture);
 	AddVariablePredef(m_GUIVar, editor_button_pause_texture); 

+ 11 - 2
Praxis3D/Source/Config.h

@@ -27,6 +27,9 @@ constexpr bool CheckBitmask(const BitMask p_bitmask, const BitMask p_flag) { ret
 enum DataType : uint32_t
 {
 	DataType_Null = 0,
+	// General
+	DataType_CreateComponent,
+	DataType_DeleteComponent,
 	// Graphics
 	DataType_GUIPassFunctors,
 	DataType_RenderToTexture,
@@ -958,6 +961,7 @@ public:
 			editor_asset_texture_button_size_y = 60.0f;
 			editor_audio_banks_max_height = 100.0f;
 			editor_float_slider_speed = 0.01f;
+			editor_inspector_button_width_multiplier = 1.5f;
 			editor_lua_variables_max_height = 200.0f;
 			editor_play_button_size = 30.0f;
 			editor_render_pass_max_height = 250.0f;
@@ -971,14 +975,16 @@ public:
 			editor_button_arrow_down_texture = "buttons\\button_arrow_down_1.png";
 			editor_button_arrow_up_texture = "buttons\\button_arrow_up_1.png";
 			editor_button_delete_entry_texture = "buttons\\button_delete_5.png";
-			editor_button_gui_sequence_texture = "buttons\\button_gui_sequence_1.png";
+			editor_button_gui_sequence_texture = "buttons\\button_gui_sequence_5.png";
+			editor_button_guizmo_rotate_texture = "buttons\\button_guizmo_rotate_1.png";
+			editor_button_guizmo_translate_texture = "buttons\\button_guizmo_translate_2.png";
 			editor_button_open_file_texture = "buttons\\button_open_file_1.png";
 			editor_button_open_asset_list_texture = "buttons\\button_add_from_list_1.png";
 			editor_button_pause_texture = "buttons\\button_editor_pause_2.png";
 			editor_button_play_texture = "buttons\\button_editor_play_2.png";
 			editor_button_reload_texture = "buttons\\button_reload_3.png";
 			editor_button_restart_texture = "buttons\\button_editor_restart_2.png";
-			editor_button_scripting_enabled_texture = "buttons\\button_scripting_1.png";
+			editor_button_scripting_enabled_texture = "buttons\\button_scripting_3.png";
 			gui_editor_window_name = "Editor window";
 		}
 		bool gui_docking_enabled;
@@ -990,6 +996,7 @@ public:
 		float editor_asset_texture_button_size_y;
 		float editor_audio_banks_max_height;
 		float editor_float_slider_speed;
+		float editor_inspector_button_width_multiplier;
 		float editor_lua_variables_max_height;
 		float editor_play_button_size;
 		float editor_render_pass_max_height;
@@ -1004,6 +1011,8 @@ public:
 		std::string editor_button_arrow_up_texture;
 		std::string editor_button_delete_entry_texture;
 		std::string editor_button_gui_sequence_texture;
+		std::string editor_button_guizmo_rotate_texture;
+		std::string editor_button_guizmo_translate_texture;
 		std::string editor_button_open_file_texture;
 		std::string editor_button_open_asset_list_texture;
 		std::string editor_button_pause_texture;

+ 10 - 0
Praxis3D/Source/Containers.h

@@ -186,4 +186,14 @@ struct EngineChangeData
 	EngineStateType m_engineStateType;
 	std::string m_filename;
 	PropertySet m_sceneProperties;
+};
+
+// Stores an entity ID and a component type, used for sending data to create / delete components
+struct EntityAndComponent
+{
+	//EntityAndComponent() : m_entityID(NULL_ENTITY_ID), m_componentType(ComponentType::ComponentType_None) { }
+	EntityAndComponent(const EntityID p_entityID, const ComponentType p_componentType) : m_entityID(p_entityID), m_componentType(p_componentType) { }
+
+	EntityID m_entityID;
+	ComponentType m_componentType;
 };

+ 488 - 22
Praxis3D/Source/EditorWindow.cpp

@@ -4,6 +4,7 @@
 #include "EngineDefinitions.h"
 #include "EditorWindow.h"
 #include "imgui_internal.h"
+#include "ImGuizmo.h"
 #include "RendererScene.h"
 #include "ShaderUniformUpdater.h"
 #include "WorldScene.h"
@@ -40,6 +41,8 @@ EditorWindow::~EditorWindow()
 
 ErrorCode EditorWindow::init()
 {
+    ImGuizmo::SetImGuiContext(ImGui::GetCurrentContext());
+    ImGuizmo::SetOrthographic(false);
     return ErrorCode::Success;
 }
 
@@ -52,6 +55,10 @@ void EditorWindow::update(const float p_deltaTime)
     m_textureInspectorSequence.swapBuffer();
     m_textureInspectorSequence.getFront().clear();
 
+    // Clear the entity and component creation / deletion pools left from the previous frame
+    clearEntityAndComponentPool();
+    clearConstructionInfoPool();
+
     // Update the entity list
     updateEntityList(); 
     
@@ -297,7 +304,7 @@ void EditorWindow::update(const float p_deltaTime)
                 m_playPauseButtonSize,
                 ImVec2(0, 1),
                 ImVec2(1, 0),
-                m_GUISequenceEnabled ? ImVec4(0.26f, 0.26f, 0.26f, 1.0f) : ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))
+                m_GUISequenceEnabled ? m_buttonBackgroundEnabled : m_buttonBackgroundDisabled))
             {
                 m_GUISequenceEnabled = !m_GUISequenceEnabled;
 
@@ -306,15 +313,15 @@ void EditorWindow::update(const float p_deltaTime)
             }
             if(ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay))
                 ImGui::SetTooltip(m_GUISequenceEnabled ? "Click to disable GUI Sequence components drawing on screen" : "Click to enable GUI Sequence components drawing on screen", ImGui::GetStyle().HoverDelayShort);
-            ImGui::SameLine();
 
             // Draw ENABLE SCRIPTING button
+            ImGui::SameLine();
             if(ImGui::ImageButton("##ScriptingEnableButton",
                 (ImTextureID)m_buttonTextures[ButtonTextureType::ButtonTextureType_ScriptingEnable].getHandle(),
                 m_playPauseButtonSize,
                 ImVec2(0, 1),
                 ImVec2(1, 0),
-                m_LUAScriptingEnabled ? ImVec4(0.26f, 0.26f, 0.26f, 1.0f) : ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))
+                m_LUAScriptingEnabled ? m_buttonBackgroundEnabled : m_buttonBackgroundDisabled))
             {
                 m_LUAScriptingEnabled = !m_LUAScriptingEnabled;
 
@@ -324,6 +331,34 @@ void EditorWindow::update(const float p_deltaTime)
             if(ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay))
                 ImGui::SetTooltip(m_LUAScriptingEnabled ? "Click to disable LUA scripting components" : "Click to enable LUA scripting components", ImGui::GetStyle().HoverDelayShort);
 
+            // Draw GUIZMO TRANSLATE button
+            ImGui::SameLine();
+            if(ImGui::ImageButton("##GuizmoTranslateEnableButton",
+                (ImTextureID)m_buttonTextures[ButtonTextureType::ButtonTextureType_GuizmoTranslate].getHandle(),
+                m_playPauseButtonSize,
+                ImVec2(0, 1),
+                ImVec2(1, 0),
+                m_translateGuizmoEnabled ? m_buttonBackgroundEnabled : m_buttonBackgroundDisabled))
+            {
+                m_translateGuizmoEnabled = !m_translateGuizmoEnabled;
+            }
+            if(ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay))
+                ImGui::SetTooltip(m_translateGuizmoEnabled ? "Click to stop showing position control" : "Click to show position control", ImGui::GetStyle().HoverDelayShort);
+
+            // Draw GUIZMO ROTATE button
+            ImGui::SameLine();
+            if(ImGui::ImageButton("##GuizmoRotateEnableButton",
+                (ImTextureID)m_buttonTextures[ButtonTextureType::ButtonTextureType_GuizmoRotate].getHandle(),
+                m_playPauseButtonSize,
+                ImVec2(0, 1),
+                ImVec2(1, 0),
+                m_rotateGuizmoEnabled ? m_buttonBackgroundEnabled : m_buttonBackgroundDisabled))
+            {
+                m_rotateGuizmoEnabled = !m_rotateGuizmoEnabled;
+            }
+            if(ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay))
+                ImGui::SetTooltip(m_rotateGuizmoEnabled ? "Click to stop showing rotation control" : "Click to show rotation control", ImGui::GetStyle().HoverDelayShort);
+
             // Get the secondary menu bar style, required for retrieving size information
             ImGuiStyle &secondaryMenuBarStyle = ImGui::GetStyle();
 
@@ -337,13 +372,13 @@ void EditorWindow::update(const float p_deltaTime)
             ImGui::SameLine(ImGui::GetContentRegionAvail().x / 2 - offsetToCenter);
 
             // Draw PLAY button
-            ImGui::PushStyleColor(ImGuiCol_Button, m_sceneState == EditorSceneState::EditorSceneState_Play ? ImVec4(0.26f, 0.26f, 0.26f, 1.0f) : ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+            ImGui::PushStyleColor(ImGuiCol_Button, m_sceneState == EditorSceneState::EditorSceneState_Play ? m_buttonBackgroundEnabled : m_buttonBackgroundDisabled);
             if(ImGui::ImageButton("##PlayButton", 
                 (ImTextureID)m_buttonTextures[ButtonTextureType::ButtonTextureType_Play].getHandle(), 
                 m_playPauseButtonSize,
                 ImVec2(0, 1),
                 ImVec2(1, 0),
-                m_sceneState == EditorSceneState::EditorSceneState_Play ? ImVec4(0.26f, 0.26f, 0.26f, 1.0f) : ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))
+                m_sceneState == EditorSceneState::EditorSceneState_Play ? m_buttonBackgroundEnabled : m_buttonBackgroundDisabled))
             {
                 m_sceneState = EditorSceneState::EditorSceneState_Play;
 
@@ -353,14 +388,14 @@ void EditorWindow::update(const float p_deltaTime)
             ImGui::PopStyleColor();
 
             // Draw PAUSE button
-            ImGui::PushStyleColor(ImGuiCol_Button, m_sceneState == EditorSceneState::EditorSceneState_Pause ? ImVec4(0.26f, 0.26f, 0.26f, 1.0f) : ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+            ImGui::PushStyleColor(ImGuiCol_Button, m_sceneState == EditorSceneState::EditorSceneState_Pause ? m_buttonBackgroundEnabled : m_buttonBackgroundDisabled);
             ImGui::SameLine();
             if(ImGui::ImageButton("##PauseButton", 
                 (ImTextureID)m_buttonTextures[ButtonTextureType::ButtonTextureType_Pause].getHandle(), 
                 m_playPauseButtonSize,
                 ImVec2(0, 1),
                 ImVec2(1, 0),
-                m_sceneState == EditorSceneState::EditorSceneState_Pause ? ImVec4(0.26f, 0.26f, 0.26f, 1.0f) : ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))
+                m_sceneState == EditorSceneState::EditorSceneState_Pause ? m_buttonBackgroundEnabled : m_buttonBackgroundDisabled))
             {
                 m_sceneState = EditorSceneState::EditorSceneState_Pause;
 
@@ -482,9 +517,15 @@ void EditorWindow::update(const float p_deltaTime)
             {
                 if(m_selectedEntity)
                 {
+                    // Clear the component exist flags as they are reset every frame
+                    m_selectedEntity.clearComponentExistFlags();
+
                     // Calculate widget offset used to draw a label on the left and a widget on the right (opposite of how ImGui draws it)
                     float inputWidgetOffset = ImGui::GetCursorPosX() + ImGui::CalcItemWidth() * 0.5f + ImGui::GetStyle().ItemInnerSpacing.x;
 
+                    // Calculate the offset for the collapsing header that is drawn after the delete component button of each component type
+                    const float headerOffsetAfterDeleteButton = m_buttonSizedByFont.x + m_imguiStyle.FramePadding.x * 4.0f;
+
                     // WORLD COMPONENTS
                     auto *metadataComponent = entityRegistry.try_get<MetadataComponent>(m_selectedEntity.m_entityID);
                     if(metadataComponent != nullptr)
@@ -503,10 +544,22 @@ void EditorWindow::update(const float p_deltaTime)
                             }
                         }
                     }
-
                     auto *spatialComponent = entityRegistry.try_get<SpatialComponent>(m_selectedEntity.m_entityID);
                     if(spatialComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_SpatialComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##SpatialComponentDeleteButton", "Delete the Spatial component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_SpatialComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::World), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::SpatialComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             auto *rigidBodyComponent = entityRegistry.try_get<RigidBodyComponent>(m_selectedEntity.m_entityID);
@@ -514,6 +567,15 @@ void EditorWindow::update(const float p_deltaTime)
                             // Get the current spatial data from the selected entity spatial component
                             m_selectedEntity.m_spatialDataManager = spatialComponent->getSpatialDataChangeManager();
 
+                            // Draw ACTIVE
+                            m_selectedEntity.m_componentData.m_worldComponents.m_spatialConstructionInfo->m_active = spatialComponent->isObjectActive();
+                            drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                            if(ImGui::Checkbox("##SpatialComponentActive", &m_selectedEntity.m_componentData.m_worldComponents.m_spatialConstructionInfo->m_active))
+                            {
+                                // If the active flag was changed, send a notification to the Spatial Component
+                                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))
@@ -552,15 +614,36 @@ void EditorWindow::update(const float p_deltaTime)
                             }
                         }
                     }
-
                     auto *objectMaterialComponent = entityRegistry.try_get<ObjectMaterialComponent>(m_selectedEntity.m_entityID);
                     if(objectMaterialComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_ObjectMaterialComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##ObjectMaterialComponentDeleteButton", "Delete the Object Material component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_ObjectMaterialComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::World), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::ObjectMaterialComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             // Get the current object material type from the selected entity Object Material Component
                             m_selectedEntity.m_objectMaterialType = objectMaterialComponent->getObjectMaterialType();
-                            
+
+                            // Draw ACTIVE
+                            m_selectedEntity.m_componentData.m_worldComponents.m_objectMaterialConstructionInfo->m_active = objectMaterialComponent->isObjectActive();
+                            drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                            if(ImGui::Checkbox("##ObjectMaterialComponentActive", &m_selectedEntity.m_componentData.m_worldComponents.m_objectMaterialConstructionInfo->m_active))
+                            {
+                                // If the active flag was changed, send a notification to the Object Material Component
+                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, objectMaterialComponent, Systems::Changes::Generic::Active);
+                            }
+
                             // Draw OBJECT MATERIAL TYPE
                             drawLeftAlignedLabelText("Material type:", inputWidgetOffset);
                             if(ImGui::Combo("##ObjectMaterialTypePicker", &m_selectedEntity.m_objectMaterialType, &m_physicalMaterialProperties[0], ObjectMaterialType::NumberOfMaterialTypes))
@@ -575,6 +658,19 @@ void EditorWindow::update(const float p_deltaTime)
                     auto *cameraComponent = entityRegistry.try_get<CameraComponent>(m_selectedEntity.m_entityID);
                     if(cameraComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_CameraComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##CameraComponentDeleteButton", "Delete the Camera component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_CameraComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::CameraComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             m_selectedEntity.m_componentData.m_graphicsComponents.m_cameraConstructionInfo->m_active = cameraComponent->isObjectActive();
@@ -591,11 +687,33 @@ void EditorWindow::update(const float p_deltaTime)
                     auto *lightComponent = entityRegistry.try_get<LightComponent>(m_selectedEntity.m_entityID);
                     if(lightComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_LightComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##LightComponentDeleteButton", "Delete the Light component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_LightComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::LightComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             const char *lightTypeStrings[] = { "null", "Directional", "Point", "Spot" };
                             m_selectedEntity.m_lightType = lightComponent->getLightType();
 
+                            // Draw ACTIVE
+                            m_selectedEntity.m_componentData.m_graphicsComponents.m_lightConstructionInfo->m_active = lightComponent->isObjectActive();
+                            drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                            if(ImGui::Checkbox("##LightComponentActive", &m_selectedEntity.m_componentData.m_graphicsComponents.m_lightConstructionInfo->m_active))
+                            {
+                                // If the active flag was changed, send a notification to the Light Component
+                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, lightComponent, Systems::Changes::Generic::Active);
+                            }
+
                             // Draw LIGHT TYPE
                             drawLeftAlignedLabelText("Light type:", inputWidgetOffset);
                             if(ImGui::Combo("##LightTypePicker", &m_selectedEntity.m_lightType, lightTypeStrings, 4))
@@ -702,6 +820,19 @@ void EditorWindow::update(const float p_deltaTime)
                     auto *modelComponent = entityRegistry.try_get<ModelComponent>(m_selectedEntity.m_entityID);
                     if(modelComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_ModelComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##ModelComponentDeleteButton", "Delete the Model component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_ModelComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::ModelComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             bool modelComponentDataNeedsUpdating = false;
@@ -740,6 +871,15 @@ void EditorWindow::update(const float p_deltaTime)
                                 modelComponent->getModelsProperties(m_selectedEntity.m_componentData.m_graphicsComponents.m_modelConstructionInfo->m_modelsProperties);
                             }
 
+                            // Draw ACTIVE
+                            m_selectedEntity.m_componentData.m_graphicsComponents.m_modelConstructionInfo->m_active = modelComponent->isObjectActive();
+                            drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                            if(ImGui::Checkbox("##ModelComponentActive", &m_selectedEntity.m_componentData.m_graphicsComponents.m_modelConstructionInfo->m_active))
+                            {
+                                // If the active flag was changed, send a notification to the Model Component
+                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, modelComponent, Systems::Changes::Generic::Active);
+                            }
+
                             // Go over each model
                             for(decltype(m_selectedEntity.m_componentData.m_graphicsComponents.m_modelConstructionInfo->m_modelsProperties.m_models.size()) modelSize = m_selectedEntity.m_componentData.m_graphicsComponents.m_modelConstructionInfo->m_modelsProperties.m_models.size(), 
                                 modelIndex = 0; modelIndex < modelSize; modelIndex++)
@@ -756,7 +896,7 @@ void EditorWindow::update(const float p_deltaTime)
 
                                 // Draw MODEL OPEN button
                                 ImGui::SameLine(calcTextSizedButtonOffset(2));
-                                if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_OpenFile], "##ModelFileOpenButton", "Open a model file"))
+                                if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_OpenFile], "##" + Utilities::toString(modelIndex) + "ModelFileOpenButton", "Open a model file"))
                                 {
                                     // Only open the file browser if it's not opened already
                                     if(m_currentlyOpenedFileBrowser == FileBrowserActivated::FileBrowserActivated_None)
@@ -999,15 +1139,51 @@ void EditorWindow::update(const float p_deltaTime)
                                 }
                                 ImGui::PopStyleVar(); // ImGuiStyleVar_SeparatorTextAlign
                             }
+
+                            // Calculate button size
+                            const char *addModelButtonLabel = "Add model";
+                            float addModelButtonWidth = ImGui::CalcTextSize(addModelButtonLabel).x * Config::GUIVar().editor_inspector_button_width_multiplier;
+
+                            // Set the button position to the right-most side
+                            ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - addModelButtonWidth);
+
+                            // Draw ADD MODEL button
+                            if(ImGui::Button(addModelButtonLabel, ImVec2(addModelButtonWidth, 0.0f)))
+                            {
+                                // Add an empty model entry
+                                m_selectedEntity.m_componentData.m_graphicsComponents.m_modelConstructionInfo->m_modelsProperties.m_models.push_back(ModelComponent::MeshProperties());
+
+                                // Set the modified flag
+                                m_selectedEntity.m_modelDataModified = true;
+                            }
                         }
                     }
-
                     auto *shaderComponent = entityRegistry.try_get<ShaderComponent>(m_selectedEntity.m_entityID);
                     if(shaderComponent != nullptr)
                     {
-                        if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::ShaderComponent), ImGuiTreeNodeFlags_DefaultOpen))
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_ShaderComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##ShaderComponentDeleteButton", "Delete the Shader component"))
                         {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_ShaderComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
 
+                        if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::ShaderComponent), ImGuiTreeNodeFlags_DefaultOpen))
+                        {
+                            // Draw ACTIVE
+                            m_selectedEntity.m_componentData.m_graphicsComponents.m_shaderConstructionInfo->m_active = shaderComponent->isObjectActive();
+                            drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                            if(ImGui::Checkbox("##ShaderComponentActive", &m_selectedEntity.m_componentData.m_graphicsComponents.m_shaderConstructionInfo->m_active))
+                            {
+                                // If the active flag was changed, send a notification to the Shader Component
+                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, shaderComponent, Systems::Changes::Generic::Active);
+                            }
                         }
                     }
 
@@ -1023,6 +1199,19 @@ void EditorWindow::update(const float p_deltaTime)
                     auto *rigidBodyComponent = entityRegistry.try_get<RigidBodyComponent>(m_selectedEntity.m_entityID);
                     if(rigidBodyComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_RigidBodyComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##RigidBodyComponentDeleteButton", "Delete the Rigid Body component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_RigidBodyComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Physics), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::RigidBodyComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             // Get the bullet physics rigid body object
@@ -1035,6 +1224,15 @@ void EditorWindow::update(const float p_deltaTime)
                             m_selectedEntity.m_componentData.m_physicsComponents.m_rigidBodyConstructionInfo->m_restitution = rigidBody->getRestitution();
                             m_selectedEntity.m_componentData.m_physicsComponents.m_rigidBodyConstructionInfo->m_kinematic = rigidBody->isKinematicObject();
 
+                            // Draw ACTIVE
+                            m_selectedEntity.m_componentData.m_physicsComponents.m_rigidBodyConstructionInfo->m_active = rigidBodyComponent->isObjectActive();
+                            drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                            if(ImGui::Checkbox("##RigidBodyComponentActive", &m_selectedEntity.m_componentData.m_physicsComponents.m_rigidBodyConstructionInfo->m_active))
+                            {
+                                // If the active flag was changed, send a notification to the Rigid Body Component
+                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Generic::Active);
+                            }
+
                             // Draw KINEMATIC
                             drawLeftAlignedLabelText("Kinematic:", inputWidgetOffset);
                             if(ImGui::Checkbox("##KinematicCheck", &m_selectedEntity.m_componentData.m_physicsComponents.m_rigidBodyConstructionInfo->m_kinematic))
@@ -1142,6 +1340,19 @@ void EditorWindow::update(const float p_deltaTime)
                     auto *soundComponent = entityRegistry.try_get<SoundComponent>(m_selectedEntity.m_entityID);
                     if(soundComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_SoundComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##SoundComponentDeleteButton", "Delete the Sound component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_SoundComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Audio), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::SoundComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             // Get Sound Component data
@@ -1163,8 +1374,16 @@ void EditorWindow::update(const float p_deltaTime)
                             else
                                 m_selectedEntity.m_componentData.m_audioComponents.m_soundConstructionInfo->m_soundFilename = soundComponent->getSoundFilename();
 
+                            // Draw ACTIVE
+                            m_selectedEntity.m_componentData.m_audioComponents.m_soundConstructionInfo->m_active = soundComponent->isObjectActive();
+                            drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                            if(ImGui::Checkbox("##SoundComponentActive", &m_selectedEntity.m_componentData.m_audioComponents.m_soundConstructionInfo->m_active))
+                            {
+                                // If the active flag was changed, send a notification to the Sound Component
+                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, soundComponent, Systems::Changes::Generic::Active);
+                            }
+
                             // Draw SOUND FILENAME
-                            //drawLeftAlignedLabelText("Filename:", inputWidgetOffset, ImGui::GetContentRegionAvail().x - inputWidgetOffset - (openReloadButtonSize.x + m_imguiStyle.FramePadding.x * 2) * 2);
                             drawLeftAlignedLabelText("Filename:", inputWidgetOffset, calcTextSizedButtonOffset(1) - inputWidgetOffset - m_imguiStyle.FramePadding.x);
                             if(ImGui::InputText("##SoundFilenameInput", &m_selectedEntity.m_componentData.m_audioComponents.m_soundConstructionInfo->m_soundFilename, ImGuiInputTextFlags_EnterReturnsTrue))
                             {
@@ -1246,10 +1465,32 @@ void EditorWindow::update(const float p_deltaTime)
                     auto *soundListenerComponent = entityRegistry.try_get<SoundListenerComponent>(m_selectedEntity.m_entityID);
                     if(soundListenerComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_SoundListenerComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##SoundListenerComponentDeleteButton", "Delete the Sound Listener component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_SoundListenerComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Audio), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::SoundListenerComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             m_selectedEntity.m_componentData.m_audioComponents.m_soundListenerConstructionInfo->m_listenerID = soundListenerComponent->getListenerID();
 
+                            // Draw ACTIVE
+                            m_selectedEntity.m_componentData.m_audioComponents.m_soundListenerConstructionInfo->m_active = soundListenerComponent->isObjectActive();
+                            drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                            if(ImGui::Checkbox("##SoundListenerComponentActive", &m_selectedEntity.m_componentData.m_audioComponents.m_soundListenerConstructionInfo->m_active))
+                            {
+                                // If the active flag was changed, send a notification to the Sound Listener Component
+                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, soundListenerComponent, Systems::Changes::Generic::Active);
+                            }
+
                             // Draw SOUND LISTENER ID
                             drawLeftAlignedLabelText("Listener ID:", inputWidgetOffset);
                             ImGui::InputInt("##ListenerIDInput", &m_selectedEntity.m_componentData.m_audioComponents.m_soundListenerConstructionInfo->m_listenerID);
@@ -1264,6 +1505,19 @@ void EditorWindow::update(const float p_deltaTime)
                     auto *luaComponent = entityRegistry.try_get<LuaComponent>(m_selectedEntity.m_entityID);
                     if(luaComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_LuaComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##LUAComponentDeleteButton", "Delete the LUA component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_LuaComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Script), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::LuaComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             auto luaScript = luaComponent->getLuaScript();
@@ -1280,6 +1534,15 @@ void EditorWindow::update(const float p_deltaTime)
                                 else
                                     m_selectedEntity.m_componentData.m_scriptComponents.m_luaConstructionInfo->m_luaScriptFilename = luaScript->getLuaScriptFilename();
 
+                                // Draw ACTIVE
+                                m_selectedEntity.m_componentData.m_scriptComponents.m_luaConstructionInfo->m_active = luaComponent->isObjectActive();
+                                drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                                if(ImGui::Checkbox("##LUAComponentActive", &m_selectedEntity.m_componentData.m_scriptComponents.m_luaConstructionInfo->m_active))
+                                {
+                                    // If the active flag was changed, send a notification to the LUA Component
+                                    m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, luaComponent, Systems::Changes::Generic::Active);
+                                }
+
                                 // Draw LUA FILENAME
                                 drawLeftAlignedLabelText("Filename:", inputWidgetOffset, calcTextSizedButtonOffset(1) - inputWidgetOffset - m_imguiStyle.FramePadding.x);
                                 if(ImGui::InputText("##LuaScriptFilenameInput", &m_selectedEntity.m_componentData.m_scriptComponents.m_luaConstructionInfo->m_luaScriptFilename, ImGuiInputTextFlags_EnterReturnsTrue))
@@ -1556,11 +1819,33 @@ void EditorWindow::update(const float p_deltaTime)
                     auto *guiSequenceComponent = entityRegistry.try_get<GUISequenceComponent>(m_selectedEntity.m_entityID);
                     if(guiSequenceComponent != nullptr)
                     {
+                        // Set the corresponding component type to be existing
+                        m_selectedEntity.m_componentTypeText[ComponentType::ComponentType_GUISequenceComponent].second = true;
+
+                        // Draw DELETE COMPONENT button
+                        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], "##GUISequenceDeleteButton", "Delete the GUI Sequence component"))
+                        {
+                            // Create a container with the entity ID and the component type, add it to the pool (so it can be deleted next frame) and send a Delete Component change with the attached container
+                            EntityAndComponent *deleteComponentData = new EntityAndComponent(m_selectedEntity.m_entityID, ComponentType::ComponentType_GUISequenceComponent);
+                            m_entityAndComponentPool.push_back(deleteComponentData);
+                            m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::GUI), DataType::DataType_DeleteComponent, (void *)deleteComponentData, false);
+                        }
+                        ImGui::SameLine(headerOffsetAfterDeleteButton);
+
                         if(ImGui::CollapsingHeader(GetString(Properties::PropertyID::GUISequenceComponent), ImGuiTreeNodeFlags_DefaultOpen))
                         {
                             // Get the current GUI Sequence data
                             m_selectedEntity.m_componentData.m_guiComponents.m_guiSequenceConstructionInfo->m_staticSequence = guiSequenceComponent->isStaticSequence();
 
+                            // Draw ACTIVE
+                            m_selectedEntity.m_componentData.m_guiComponents.m_guiSequenceConstructionInfo->m_active = guiSequenceComponent->isObjectActive();
+                            drawLeftAlignedLabelText("Active:", inputWidgetOffset);
+                            if(ImGui::Checkbox("##GUISequenceComponentActive", &m_selectedEntity.m_componentData.m_guiComponents.m_guiSequenceConstructionInfo->m_active))
+                            {
+                                // If the active flag was changed, send a notification to the GUI Sequence Component
+                                m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, guiSequenceComponent, Systems::Changes::Generic::Active);
+                            }
+
                             // Draw STATIC
                             drawLeftAlignedLabelText("Static sequence:", inputWidgetOffset);
                             if(ImGui::Checkbox("##StaticSequenceCheck", &m_selectedEntity.m_componentData.m_guiComponents.m_guiSequenceConstructionInfo->m_staticSequence))
@@ -1570,7 +1855,131 @@ void EditorWindow::update(const float p_deltaTime)
                             }
                         }
                     }
-                    
+
+                    const std::string componentTypeSelectionPopupName = "##ComponentTypeSelectionPopup";
+
+                    ImGui::NewLine();
+                    ImGui::Separator();
+
+                    // Calculate button size
+                    const char *addComponentButtonLabel = "Add component";
+                    float addComponentButtonWidth = ImGui::CalcTextSize(addComponentButtonLabel).x * Config::GUIVar().editor_inspector_button_width_multiplier;
+
+                    // Set the button position to the right-most side
+                    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - addComponentButtonWidth);
+
+                    // Draw ADD COMPONENT button
+                    if(ImGui::Button(addComponentButtonLabel, ImVec2(addComponentButtonWidth, 0.0f)))
+                    {
+                        // Open the pop-up with the component type list
+                        ImGui::OpenPopup(componentTypeSelectionPopupName.c_str());
+                    }
+
+                    // Draw COMPONENT TYPE LIST
+                    if(ImGui::BeginPopup(componentTypeSelectionPopupName.c_str()))
+                    {
+                        // Make an array of component types and type text
+                        std::vector<std::pair<std::string, ComponentType>> componentTypes;
+
+                        // Populate the array with component types that aren't present in this entity
+                        for(unsigned int i = 0; i < ComponentType::ComponentType_NumOfTypes; i++)
+                            if(!m_selectedEntity.m_componentTypeText[i].second)
+                                componentTypes.push_back(std::make_pair(m_selectedEntity.m_componentTypeText[i].first, static_cast<ComponentType>(i)));
+
+                        // Sort the array alphabetically (based on component type text)
+                        std::sort(componentTypes.begin(), componentTypes.end());
+
+                        // Remove selection border and align text vertically
+                        ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.0f, 0.5f));
+                        ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
+
+                        // Go over each non-present component type
+                        for(decltype(componentTypes.size()) i = 0, size = componentTypes.size(); i < size; i++)
+                        {
+                            if(ImGui::Selectable(componentTypes[i].first.c_str(), false, 0, ImVec2(0.0f, m_fontSize * 2.0f)))
+                            {
+                                std::cout << componentTypes[i].first << std::endl;
+
+                                ComponentsConstructionInfo *newComponentInfo = new ComponentsConstructionInfo();
+                                bool newComponentInfoSet = true;
+
+                                switch(componentTypes[i].second)
+                                {
+                                    case ComponentType::ComponentType_SoundComponent:
+                                        {
+                                            newComponentInfo->m_audioComponents.m_soundConstructionInfo = new SoundComponent::SoundComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_SoundListenerComponent:
+                                        {
+                                            newComponentInfo->m_audioComponents.m_soundListenerConstructionInfo = new SoundListenerComponent::SoundListenerComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_CameraComponent:
+                                        {
+                                            newComponentInfo->m_graphicsComponents.m_cameraConstructionInfo = new CameraComponent::CameraComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_LightComponent:
+                                        {
+                                            newComponentInfo->m_graphicsComponents.m_lightConstructionInfo = new LightComponent::LightComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_ModelComponent:
+                                        {
+                                            newComponentInfo->m_graphicsComponents.m_modelConstructionInfo = new ModelComponent::ModelComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_ShaderComponent:
+                                        {
+                                            newComponentInfo->m_graphicsComponents.m_shaderConstructionInfo = new ShaderComponent::ShaderComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_GUISequenceComponent:
+                                        {
+                                            newComponentInfo->m_guiComponents.m_guiSequenceConstructionInfo = new GUISequenceComponent::GUISequenceComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_RigidBodyComponent:
+                                        {
+                                            newComponentInfo->m_physicsComponents.m_rigidBodyConstructionInfo = new RigidBodyComponent::RigidBodyComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_LuaComponent:
+                                        {
+                                            newComponentInfo->m_scriptComponents.m_luaConstructionInfo = new LuaComponent::LuaComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_ObjectMaterialComponent:
+                                        {
+                                            newComponentInfo->m_worldComponents.m_objectMaterialConstructionInfo = new ObjectMaterialComponent::ObjectMaterialComponentConstructionInfo();
+                                        }
+                                        break;
+                                    case ComponentType::ComponentType_SpatialComponent:
+                                        {
+                                            newComponentInfo->m_worldComponents.m_spatialConstructionInfo = new SpatialComponent::SpatialComponentConstructionInfo();
+                                        }
+                                        break;
+                                    default:
+                                        {
+                                            newComponentInfoSet = false;
+                                        }
+                                        break;
+                                }
+
+                                if(newComponentInfoSet)
+                                {
+                                    if(worldScene->addComponents(m_selectedEntity.m_entityID, *newComponentInfo) == ErrorCode::Success)
+                                        std::cout << "success" << std::endl;
+                                }
+                                else
+                                    delete newComponentInfo;
+                            }
+                        }
+                        
+                        ImGui::PopStyleVar(2); //ImGuiStyleVar_SelectableTextAlign, ImGuiStyleVar_FramePadding
+                        ImGui::EndPopup();
+                    }
                 }
                 ImGui::EndTabItem();
             }
@@ -1893,6 +2302,11 @@ void EditorWindow::update(const float p_deltaTime)
             {
                 if(ImGui::BeginTabItem("Scene viewport"))
                 {
+                    //	 ____________________________
+                    //	|							 |
+                    //	|	     DRAW SCENE          |
+                    //	|____________________________|
+                    //
                     // Get window starting position and the size of available space inside the window
                     auto windowPosition = ImGui::GetCursorScreenPos();
                     auto contentRegionSize = ImGui::GetContentRegionAvail();
@@ -1925,6 +2339,59 @@ void EditorWindow::update(const float p_deltaTime)
                     );
                     ImGui::PopStyleVar();
 
+                    //	 ____________________________
+                    //	|							 |
+                    //	|  DRAW MANIPULATION GUIZMO  |
+                    //	|____________________________|
+                    //
+                    // Tell ImGuizmo to render inside the current window
+                    ImGuizmo::SetDrawlist();
+
+                    // Set the screen size for ImGuizmo
+                    ImGuizmo::SetRect(windowPosition.x, windowPosition.y, contentRegionSize.x, contentRegionSize.y);
+
+                    // Draw ImGuizmo only if there's an entity selected
+                    if(m_selectedEntity)
+                    {
+                        // Draw ImGuizmo only if the selected entity has a spatial component
+                        auto *spatialComponent = entityRegistry.try_get<SpatialComponent>(m_selectedEntity.m_entityID);
+                        if(spatialComponent != nullptr)
+                        {
+                            ImGuizmo::Enable(true);
+
+                            // Get the renderer scene (required for view and projection matrices)
+                            auto const *rendererScene = static_cast<RendererScene*>(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics));
+
+                            // Try to get the rigid body component for sending spatial changes
+                            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();
+
+                            // 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 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)
+                                    m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalTransformNoScale);
+                                else
+                                    m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::LocalTransformNoScale);
+                            }
+
+                            // 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 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)
+                                    m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, rigidBodyComponent, Systems::Changes::Spatial::LocalTransformNoScale);
+                                else
+                                    m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, spatialComponent, Systems::Changes::Spatial::LocalTransformNoScale);
+                            }
+                        }
+                        else
+                            ImGuizmo::Enable(false);
+                    }
+
                     // If the mouse is hovering over the viewport, stop capturing it, so the engine can handle mouse events
                     if(ImGui::IsItemHovered())
                         ImGui::CaptureMouseFromApp(false);
@@ -1980,6 +2447,7 @@ void EditorWindow::update(const float p_deltaTime)
                     }
                 }
 
+                // Show the New Scene Settings tab only when New Scene button was pressed
                 if(m_showNewMapWindow)
                 {
                     if(ImGui::BeginTabItem("New scene settings", 0, m_newSceneSettingsTabFlags))
@@ -2026,7 +2494,7 @@ void EditorWindow::update(const float p_deltaTime)
                             const ImVec2 buttonSize(contentRegionSize.x * 0.2f, ImGui::GetFrameHeight() * 1.5f);
                             const float centerButtonOffset = (ImGui::GetContentRegionAvail().x / 2.0f) - (buttonSize.x / 2.0f);
 
-                            //ImGui::NewLine();
+                            // Draw CREATE SCENE button
                             ImGui::SetCursorPosX(centerButtonOffset);
                             if(ImGui::Button("Create scene", buttonSize))
                             {
@@ -2038,6 +2506,7 @@ void EditorWindow::update(const float p_deltaTime)
                                 m_systemScene->getSceneLoader()->getChangeController()->sendEngineChange(EngineChangeData(EngineChangeType::EngineChangeType_SceneReload, EngineStateType::EngineStateType_Editor, sceneProperties));
                             }
 
+                            // Draw CANCEL button
                             ImGui::SetCursorPosX(centerButtonOffset);
                             if(ImGui::Button("Cancel", buttonSize))
                             {
@@ -2045,6 +2514,7 @@ void EditorWindow::update(const float p_deltaTime)
                                 m_showNewMapWindow = false;
                             }
 
+                            // Draw RELOAD TO DEFAULT button
                             ImGui::NewLine();
                             ImGui::SetCursorPosX(centerButtonOffset);
                             if(ImGui::Button("Reload to default", buttonSize))
@@ -2289,6 +2759,8 @@ void EditorWindow::setup(EditorWindowSettings &p_editorWindowSettings)
     m_buttonTextures.emplace_back(Loaders::texture2D().load(Config::GUIVar().editor_button_reload_texture));
     m_buttonTextures.emplace_back(Loaders::texture2D().load(Config::GUIVar().editor_button_open_asset_list_texture));
     m_buttonTextures.emplace_back(Loaders::texture2D().load(Config::GUIVar().editor_button_arrow_up_texture));
+    m_buttonTextures.emplace_back(Loaders::texture2D().load(Config::GUIVar().editor_button_guizmo_rotate_texture));
+    m_buttonTextures.emplace_back(Loaders::texture2D().load(Config::GUIVar().editor_button_guizmo_translate_texture));
 
     assert(m_buttonTextures.size() == ButtonTextureType::ButtonTextureType_NumOfTypes && "m_buttonTextures array is different size than the number of button textures, in EditorWindow.cpp");
 
@@ -2725,12 +3197,6 @@ void EditorWindow::updateComponentList()
     for(decltype(m_entityList.size()) size = m_entityList.size(), i = 0; i < size; i++)
     {
         // AUDIO components
-        //auto impactSoundComp = entityRegistry.try_get<ImpactSoundComponent>(m_entityList[i].m_entityID);
-        //if(impactSoundComp != nullptr)
-        //{
-        //    m_componentList.emplace_back(m_entityList[i].m_entityID, impactSoundComp->getName(), Utilities::toString(m_entityList[i].m_entityID) + Config::componentVar().component_name_separator + impactSoundComp->getName());
-        //    m_entityList[i].m_componentFlag |= Systems::AllComponentTypes::AudioImpactSoundComponent;
-        //}
         auto soundComp = entityRegistry.try_get<SoundComponent>(m_entityList[i].m_entityID);
         if(soundComp != nullptr)
         {

+ 62 - 0
Praxis3D/Source/EditorWindow.h

@@ -25,6 +25,8 @@ public:
 		m_renderSceneToTexture = true;
 		m_GUISequenceEnabled = false;
 		m_LUAScriptingEnabled = true;
+		m_translateGuizmoEnabled = true;
+		m_rotateGuizmoEnabled = false;
 		m_showNewMapWindow = false;
 		m_sceneState = EditorSceneState::EditorSceneState_Pause;
 		m_centerWindowSize = glm::ivec2(0);
@@ -55,6 +57,9 @@ public:
 		m_buttonSizedByFont = ImVec2(m_fontSize, m_fontSize);
 		m_assetSelectionPopupImageSize = ImVec2(m_fontSize, m_fontSize) * Config::GUIVar().editor_asset_selection_button_size_multiplier;
 		m_textureAssetImageSize = ImVec2(Config::GUIVar().editor_asset_texture_button_size_x, Config::GUIVar().editor_asset_texture_button_size_y);
+
+		m_buttonBackgroundEnabled = ImVec4(0.26f, 0.26f, 0.26f, 1.0f);
+		m_buttonBackgroundDisabled = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
 	}
 	~EditorWindow();
 
@@ -150,6 +155,9 @@ public:
 						case Properties::PropertyID::CameraComponent:
 							return m_selectedEntity.m_componentData.m_graphicsComponents.m_cameraConstructionInfo->m_active;
 						case Properties::PropertyID::LightComponent:
+						case Properties::PropertyID::DirectionalLight:
+						case Properties::PropertyID::PointLight:
+						case Properties::PropertyID::SpotLight:
 							return m_selectedEntity.m_componentData.m_graphicsComponents.m_lightConstructionInfo->m_active;
 						case Properties::PropertyID::ModelComponent:
 							return m_selectedEntity.m_componentData.m_graphicsComponents.m_modelConstructionInfo->m_active;
@@ -169,6 +177,8 @@ public:
 							return m_selectedEntity.m_componentData.m_worldComponents.m_objectMaterialConstructionInfo->m_active;
 						case Properties::PropertyID::SpatialComponent:
 							return m_selectedEntity.m_componentData.m_worldComponents.m_spatialConstructionInfo->m_active;
+						default:
+							return true;
 					}
 				}
 				break;
@@ -460,6 +470,17 @@ private:
 
 			m_componentData.m_worldComponents.m_objectMaterialConstructionInfo = new ObjectMaterialComponent::ObjectMaterialComponentConstructionInfo();
 			m_componentData.m_worldComponents.m_spatialConstructionInfo = new SpatialComponent::SpatialComponentConstructionInfo();
+
+			// Populate the component type text array and also strip the prefix and suffix of ComponentType string
+			std::string componentTypePrefix = "ComponentType_";
+			std::string componentTypeSuffix = "Component";
+			for(unsigned int i = 0; i < ComponentType::ComponentType_NumOfTypes; i++)
+			{
+				m_componentTypeText[i].first = GetString(static_cast<ComponentType>(i));
+				m_componentTypeText[i].first = m_componentTypeText[i].first.substr(componentTypePrefix.size());
+				m_componentTypeText[i].first = Utilities::splitStringBeforeDelimiter(componentTypeSuffix, m_componentTypeText[i].first);
+				m_componentTypeText[i].first += " component";
+			}
 		}
 
 		inline operator bool() const { return m_entityID != NULL_ENTITY_ID; }
@@ -475,6 +496,8 @@ private:
 			m_selectedTextureName = nullptr;
 			m_modelDataPointer = nullptr;
 			m_modelDataUpdateAfterLoading = false;
+
+			clearComponentExistFlags();
 		}
 
 		void unselect()
@@ -485,6 +508,12 @@ private:
 			m_luaScriptFilenameModified = false;
 		}
 
+		void clearComponentExistFlags()
+		{
+			for(unsigned int i = 0; i < ComponentType::ComponentType_NumOfTypes; i++)
+				m_componentTypeText[i].second = false;
+		}
+
 		EntityID m_entityID;
 
 		ComponentsConstructionInfo m_componentData;
@@ -514,6 +543,9 @@ private:
 		// An array of external lua variables
 		std::vector<std::pair<std::string, Property>> m_luaVariables;
 
+		// An array containing component type text and flags marking whether a component type exists for the selected entity
+		std::pair<std::string, bool> m_componentTypeText[ComponentType::ComponentType_NumOfTypes];
+
 		bool m_luaVariablesModified;
 		bool m_luaScriptFilenameModified;
 		bool m_modelDataModified;
@@ -562,6 +594,8 @@ private:
 		ButtonTextureType_Reload,
 		ButtonTextureType_OpenAssetList,
 		ButtonTextureType_ArrowUp,
+		ButtonTextureType_GuizmoRotate,
+		ButtonTextureType_GuizmoTranslate,
 		ButtonTextureType_NumOfTypes
 	};
 	enum EditorSceneState : unsigned int
@@ -653,6 +687,26 @@ private:
 		return m_buttonSizedByFont.x + m_imguiStyle.FramePadding.x + (m_buttonSizedByFont.x + m_imguiStyle.FramePadding.x * 3) * p_buttonIndex;
 	}
 
+	void clearEntityAndComponentPool()
+	{
+		if(!m_entityAndComponentPool.empty())
+		{
+			for(decltype(m_entityAndComponentPool.size()) i = 0, size = m_entityAndComponentPool.size(); i < size; i++)
+				delete m_entityAndComponentPool[i];
+
+			m_entityAndComponentPool.clear();
+		}
+	}
+	void clearConstructionInfoPool()
+	{
+		if(!m_componentConstructionInfoPool.empty())
+		{
+			for(decltype(m_componentConstructionInfoPool.size()) i = 0, size = m_componentConstructionInfoPool.size(); i < size; i++)
+				delete m_componentConstructionInfoPool[i];
+
+			m_componentConstructionInfoPool.clear();
+		}
+	}
 	void updateSceneData(SceneData &p_sceneData);
 	void updateEntityList();
 	void updateHierarchyList();
@@ -777,6 +831,8 @@ private:
 	bool m_renderSceneToTexture;
 	bool m_GUISequenceEnabled;
 	bool m_LUAScriptingEnabled;
+	bool m_translateGuizmoEnabled;
+	bool m_rotateGuizmoEnabled;
 	bool m_showNewMapWindow;
 	EditorSceneState m_sceneState;
 	glm::ivec2 m_centerWindowSize;
@@ -790,6 +846,8 @@ private:
 	const ImVec2 m_playPauseButtonSize;
 	ImVec2 m_assetSelectionPopupImageSize;
 	ImVec2 m_textureAssetImageSize;
+	ImVec4 m_buttonBackgroundEnabled;
+	ImVec4 m_buttonBackgroundDisabled;
 
 	// LUA variables editor data
 	std::vector<const char *> m_luaVariableTypeStrings;
@@ -817,6 +875,10 @@ private:
 	SelectedEntity m_selectedEntity;
 	SceneData m_currentSceneData;
 
+	// Used to hold entity and component data for component creation / deletion until the next frame, after sending the data as a change
+	std::vector<EntityAndComponent*> m_entityAndComponentPool;
+	std::vector<ComponentsConstructionInfo*> m_componentConstructionInfoPool;
+
 	// New scene settings
 	SceneData m_newSceneData;
 	ImGuiTabItemFlags m_newSceneSettingsTabFlags;

+ 2 - 1
Praxis3D/Source/ErrorCodes.h

@@ -95,8 +95,9 @@ DECLARE_ENUM(ErrorType, ERROR_TYPES)
 	Code(SDL_vsync_failed,) \
 	Code(Window_creation_failed,) \
 	/* World scene errors */ \
-	Code(Invalid_object_id,) \
 	Code(Duplicate_object_id,) \
+	Code(Invalid_object_id,) \
+	Code(Nonexistent_object_id,) \
 	/* Error management */ \
 	Code(NumberOfErrorCodes,) \
 	Code(CachedError,)

+ 2 - 1
Praxis3D/Source/ErrorHandler.cpp

@@ -68,8 +68,9 @@ ErrorHandler::ErrorHandler()
 	AssignErrorType(SDL_video_init_failed, FatalError);
 	AssignErrorType(SDL_vsync_failed, Warning);
 	AssignErrorType(Window_creation_failed, FatalError);
-	AssignErrorType(Invalid_object_id, Warning);
 	AssignErrorType(Duplicate_object_id, Warning);
+	AssignErrorType(Invalid_object_id, Warning);
+	AssignErrorType(Nonexistent_object_id, Warning);
 
 	// 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++)

+ 5 - 0
Praxis3D/Source/GUIHandler.h

@@ -7,6 +7,8 @@
 #include "ErrorHandlerLocator.h"
 #include "WindowLocator.h"
 
+#include "ImGuizmo.h"
+
 class GUIHandlerBase
 {
 	friend class Engine;
@@ -173,6 +175,9 @@ protected:
 
 		// Begin new GUI frame (prepares frame to receive new GUI calls)
 		ImGui::NewFrame();
+
+		// Mark the beginning of ImGuizmo frame
+		ImGuizmo::BeginFrame();
 	}
 
 	void render();

+ 80 - 34
Praxis3D/Source/GUIScene.cpp

@@ -200,6 +200,20 @@ void GUIScene::loadInBackground()
 {
 }
 
+std::vector<SystemObject *> GUIScene::getComponents(const EntityID p_entityID)
+{
+	std::vector<SystemObject *> returnVector;
+
+	// Get the entity registry 
+	auto &entityRegistry = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World))->getEntityRegistry();
+
+	auto *guiSequenceComponent = entityRegistry.try_get<GUISequenceComponent>(p_entityID);
+	if(guiSequenceComponent != nullptr)
+		returnVector.push_back(guiSequenceComponent);
+
+	return returnVector;
+}
+
 std::vector<SystemObject*> GUIScene::createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading)
 {
 	return createComponents(p_entityID, p_constructionInfo.m_guiComponents, p_startLoading);
@@ -282,47 +296,79 @@ void GUIScene::receiveData(const DataType p_dataType, void *p_data, const bool p
 {
 	switch(p_dataType)
 	{
-	case DataType_EnableGUISequence:
-		setGUISequenceEnabled(static_cast<bool>(p_data));
-		break;
+		case DataType::DataType_DeleteComponent:
+			{
+				// Get the world scene required for getting the entity registry and deleting components
+				WorldScene *worldScene = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World));
 
-	case DataType_FileBrowserDialog:
-		// Cast the sent data into the intended type and add it to the file-browser dialog queue
-		m_fileBrowserDialogs.push(static_cast<FileBrowserDialog*>(p_data));
-		break;
+				// Get the entity registry 
+				auto &entityRegistry = worldScene->getEntityRegistry();
 
-	case DataType_EditorWindow:
-		// Cast the sent data into the intended type
-		EditorWindowSettings *editorWindowSettings = static_cast<EditorWindowSettings*>(p_data);
+				// Get entity and component data
+				auto const *componentData = static_cast<EntityAndComponent *>(p_data);
 
-		// If the editor window should be enabled
-		if(editorWindowSettings->m_enabled)
-		{
-			// If the editor window doesn't exist, create it
-			if(m_editorWindow == nullptr)
-			{
-				m_editorWindow = new EditorWindow(this, Config::GUIVar().gui_editor_window_name, 0);
-				m_editorWindow->init();
-				m_editorWindow->setup(*editorWindowSettings);
+				// Delete the component based on its type
+				switch(componentData->m_componentType)
+				{
+					case ComponentType::ComponentType_GUISequenceComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<GUISequenceComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								worldScene->removeComponent<GUISequenceComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+				}
+
+				// Delete the sent data if the ownership of it was transfered
+				if(p_deleteAfterReceiving)
+					delete componentData;
 			}
-			else // If the editor window does exist, just send the settings to it
-				m_editorWindow->setup(*editorWindowSettings);
+			break;
+		case DataType_EnableGUISequence:
+			setGUISequenceEnabled(static_cast<bool>(p_data));
+			break;
+
+		case DataType_FileBrowserDialog:
+			// Cast the sent data into the intended type and add it to the file-browser dialog queue
+			m_fileBrowserDialogs.push(static_cast<FileBrowserDialog *>(p_data));
+			break;
+
+		case DataType_EditorWindow:
+			// Cast the sent data into the intended type
+			EditorWindowSettings *editorWindowSettings = static_cast<EditorWindowSettings *>(p_data);
+
+			// If the editor window should be enabled
+			if(editorWindowSettings->m_enabled)
+			{
+				// If the editor window doesn't exist, create it
+				if(m_editorWindow == nullptr)
+				{
+					m_editorWindow = new EditorWindow(this, Config::GUIVar().gui_editor_window_name, 0);
+					m_editorWindow->init();
+					m_editorWindow->setup(*editorWindowSettings);
+				}
+				else // If the editor window does exist, just send the settings to it
+					m_editorWindow->setup(*editorWindowSettings);
 
-			GUIHandlerLocator::get().enableDocking();
-		}
-		else // If the editor should be disabled
-		{
-			// If the editor window exist, delete it
-			if(m_editorWindow != nullptr)
+				GUIHandlerLocator::get().enableDocking();
+			}
+			else // If the editor should be disabled
 			{
-				delete m_editorWindow;
-				m_editorWindow = nullptr;
+				// If the editor window exist, delete it
+				if(m_editorWindow != nullptr)
+				{
+					delete m_editorWindow;
+					m_editorWindow = nullptr;
+				}
 			}
-		}
 
-		// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
-		if(p_deleteAfterReceiving)
-			delete editorWindowSettings;
-		break;
+			// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
+			if(p_deleteAfterReceiving)
+				delete editorWindowSettings;
+			break;
 	}
 }

+ 3 - 0
Praxis3D/Source/GUIScene.h

@@ -128,6 +128,9 @@ public:
 
 	void loadInBackground();
 
+	// Get all the created components of the given entity that belong to this scene
+	std::vector<SystemObject *> getComponents(const EntityID p_entityID);
+
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true);
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const GUIComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true)
 	{

+ 3 - 0
Praxis3D/Source/GUISequenceComponent.h

@@ -124,6 +124,9 @@ public:
 
 	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType) 
 	{
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+
 		if(CheckBitmask(p_changeType, Systems::Changes::GUI::Sequence))
 			m_guiSequence = p_subject->getFunctors(this, Systems::Changes::GUI::Sequence);
 

+ 3 - 3
Praxis3D/Source/LightComponent.h

@@ -165,9 +165,9 @@ public:
 
 	virtual void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType)
 	{
-		// Track what data has been modified
-		//BitMask newChanges = Systems::Changes::None;
-		//BitMask processedChange = 0;
+		// Get the active flag from the subject and set the active flag accordingly
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
 
 		// Check if the light should be enabled/disabled
 		if(CheckBitmask(p_changeType, Systems::Changes::Graphics::LightEnabled))

+ 6 - 0
Praxis3D/Source/LuaComponent.h

@@ -201,6 +201,12 @@ public:
 
 	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType) 
 	{
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+		{
+			// Get the active flag from the subject and set the active flag accordingly
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+		}
+
 		if(CheckBitmask(p_changeType, Systems::Changes::Script::Filename))
 		{
 			if(m_luaScript != nullptr)

+ 1 - 1
Praxis3D/Source/Math.h

@@ -1,7 +1,7 @@
 #pragma once
 
 //#define _USE_MATH_DEFINES
-#define GLM_FORCE_CTOR_INIT
+//#define GLM_FORCE_CTOR_INIT
 
 #include <algorithm>
 #include <bullet3/LinearMath/btMatrix3x3.h>

+ 6 - 0
Praxis3D/Source/MetadataComponent.h

@@ -43,6 +43,12 @@ public:
 
 	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType)
 	{
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+		{
+			// Get the active flag from the subject and set the active flag accordingly
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+		}
+
 		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Name))
 		{
 			setName(p_subject->getString(this, Systems::Changes::Generic::Name));

+ 6 - 2
Praxis3D/Source/ModelComponent.h

@@ -92,7 +92,7 @@ public:
 		ModelsProperties m_modelsProperties;
 	};
 
-	ModelComponent(SystemScene *p_systemScene, std::string p_name, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::Models, p_entityID)
+	ModelComponent(SystemScene *p_systemScene, std::string p_name, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::ModelComponent, p_entityID)
 	{
 		m_modelsProperties = nullptr;
 		m_modelsNeedLoading = false;
@@ -374,7 +374,11 @@ public:
 
 	BitMask getPotentialSystemChanges() { return Systems::Changes::None; }
 
-	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType) { }
+	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType)
+	{
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+	}
 
 	void receiveData(const DataType p_dataType, void *p_data, const bool p_deleteAfterReceiving = false)
 	{

+ 6 - 0
Praxis3D/Source/ObjectMaterialComponent.h

@@ -47,6 +47,12 @@ public:
 
 	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType)
 	{
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+		{
+			// Get the active flag from the subject and set the active flag accordingly
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+		}
+
 		if(CheckBitmask(p_changeType, Systems::Changes::World::ObjectMaterialType))
 		{
 			// Get the new material type from the observed subject

+ 65 - 0
Praxis3D/Source/PhysicsScene.cpp

@@ -346,6 +346,20 @@ void PhysicsScene::loadInBackground()
 {
 }
 
+std::vector<SystemObject *> PhysicsScene::getComponents(const EntityID p_entityID)
+{
+	std::vector<SystemObject *> returnVector;
+
+	// Get the entity registry 
+	auto &entityRegistry = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World))->getEntityRegistry();
+
+	auto *rigidBodyComponent = entityRegistry.try_get<RigidBodyComponent>(p_entityID);
+	if(rigidBodyComponent != nullptr)
+		returnVector.push_back(rigidBodyComponent);
+
+	return returnVector;
+}
+
 std::vector<SystemObject*> PhysicsScene::createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading)
 {
 	return createComponents(p_entityID, p_constructionInfo.m_physicsComponents, p_startLoading);
@@ -524,3 +538,54 @@ void PhysicsScene::changeOccurred(ObservedSubject *p_subject, BitMask p_changeTy
 		m_dynamicsWorld->setGravity(Math::toBtVector3(p_subject->getVec3(this, Systems::Changes::Physics::Gravity)));
 	}
 }
+
+void PhysicsScene::receiveData(const DataType p_dataType, void *p_data, const bool p_deleteAfterReceiving)
+{
+	switch(p_dataType)
+	{
+		case DataType::DataType_DeleteComponent:
+			{
+				// Get the world scene required for getting the entity registry and deleting components
+				WorldScene *worldScene = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World));
+
+				// Get the entity registry 
+				auto &entityRegistry = worldScene->getEntityRegistry();
+
+				// Get entity and component data
+				auto const *componentData = static_cast<EntityAndComponent *>(p_data);
+
+				// Delete the component based on its type
+				switch(componentData->m_componentType)
+				{
+					case ComponentType::ComponentType_RigidBodyComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<RigidBodyComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Remove rigid body from the world
+								if(component->m_rigidBody != nullptr)
+								{
+									delete component->m_rigidBody->getMotionState();
+									delete component->m_rigidBody->getCollisionShape();
+									m_dynamicsWorld->removeRigidBody(component->m_rigidBody);
+								}
+
+								// Delete component
+								worldScene->removeComponent<RigidBodyComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+				}
+
+				// Delete the sent data if the ownership of it was transfered
+				if(p_deleteAfterReceiving)
+					delete componentData;
+			}
+			break;
+
+		case DataType_SimulationActive:
+			m_simulationRunning = static_cast<bool>(p_data);
+			break;
+	}
+}

+ 4 - 9
Praxis3D/Source/PhysicsScene.h

@@ -96,6 +96,9 @@ public:
 
 	void loadInBackground();
 
+	// Get all the created components of the given entity that belong to this scene
+	std::vector<SystemObject *> getComponents(const EntityID p_entityID);
+
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true);
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const PhysicsComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true)
 	{
@@ -135,15 +138,7 @@ public:
 
 	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType);
 
-	void receiveData(const DataType p_dataType, void *p_data, const bool p_deleteAfterReceiving)
-	{
-		switch(p_dataType)
-		{
-			case DataType_SimulationActive:
-				m_simulationRunning = static_cast<bool>(p_data);
-				break;
-		}
-	}
+	void receiveData(const DataType p_dataType, void *p_data, const bool p_deleteAfterReceiving);
 
 	SystemTask *getSystemTask() { return m_physicsTask; };
 	Systems::TypeID getSystemType() { return Systems::TypeID::Physics; };

+ 2 - 0
Praxis3D/Source/RendererFrontend.h

@@ -67,6 +67,8 @@ public:
 	void renderFrame(SceneObjects &p_sceneObjects, const float p_deltaTime);
 
 	unsigned int getFramebufferTextureHandle(GBufferTextureType p_bufferType) const;
+
+	const inline UniformFrameData &getFrameData() const { return m_frameData; }
 	
 protected:
 	inline void queueForDrawing(const ModelData &p_modelData, const unsigned int p_shaderHandle, const ShaderUniformUpdater &p_uniformUpdater, const glm::mat4 &p_modelMatrix, const glm::mat4 &p_viewProjMatrix)

+ 145 - 34
Praxis3D/Source/RendererScene.cpp

@@ -430,6 +430,32 @@ void RendererScene::update(const float p_deltaTime)
 	}
 }
 
+std::vector<SystemObject *> RendererScene::getComponents(const EntityID p_entityID)
+{
+	std::vector<SystemObject *> returnVector;
+
+	// Get the entity registry 
+	auto &entityRegistry = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World))->getEntityRegistry();
+
+	auto *cameraComponent = entityRegistry.try_get<CameraComponent>(p_entityID);
+	if(cameraComponent != nullptr)
+		returnVector.push_back(cameraComponent);
+
+	auto *lightComponent = entityRegistry.try_get<LightComponent>(p_entityID);
+	if(lightComponent != nullptr)
+		returnVector.push_back(lightComponent);
+
+	auto *modelComponent = entityRegistry.try_get<ModelComponent>(p_entityID);
+	if(modelComponent != nullptr)
+		returnVector.push_back(modelComponent);
+
+	auto *shaderComponent = entityRegistry.try_get<ShaderComponent>(p_entityID);
+	if(shaderComponent != nullptr)
+		returnVector.push_back(shaderComponent);
+
+	return returnVector;
+}
+
 std::vector<SystemObject*> RendererScene::createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading)
 {
 	return createComponents(p_entityID, p_constructionInfo.m_graphicsComponents, p_startLoading);
@@ -598,8 +624,8 @@ SystemObject *RendererScene::createComponent(const EntityID &p_entityID, const M
 	SystemObject *returnObject = g_nullSystemBase.getScene(EngineStateType::EngineStateType_Default)->getNullObject();
 
 	// Make sure there are models present
-	if(!p_constructionInfo.m_modelsProperties.m_models.empty())
-	{
+	//if(!p_constructionInfo.m_modelsProperties.m_models.empty())
+	//{
 		// Get the world scene required for attaching components to the entity
 		WorldScene *worldScene = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World));
 
@@ -629,9 +655,9 @@ SystemObject *RendererScene::createComponent(const EntityID &p_entityID, const M
 			worldScene->removeComponent<ModelComponent>(p_entityID);
 			ErrHandlerLoc().get().log(componentInitError, ErrorSource::Source_ModelComponent, p_constructionInfo.m_name);
 		}
-	}
-	else
-		ErrHandlerLoc().get().log(ErrorCode::Initialize_failure, ErrorSource::Source_ModelComponent, p_constructionInfo.m_name);
+	//}
+	//else
+	//	ErrHandlerLoc().get().log(ErrorCode::Initialize_failure, ErrorSource::Source_ModelComponent, p_constructionInfo.m_name);
 
 	return returnObject;
 }
@@ -715,42 +741,117 @@ void RendererScene::receiveData(const DataType p_dataType, void *p_data, const b
 {
 	switch(p_dataType)
 	{
-	case DataType_GUIPassFunctors:
-		m_renderTask->m_renderer.setGUIPassFunctorSequence(static_cast<FunctorSequence *>(p_data));
-		break;
+		case DataType::DataType_CreateComponent:
+			{
 
-	case DataType_RenderToTexture:
-		m_renderToTexture = static_cast<bool>(p_data);
-		m_renderTask->m_renderer.setRenderFinalToTexture(m_renderToTexture);
-		break;
+			}
+			break;
 
-	case DataType_RenderToTextureResolution:
-		{
-			auto renderToTextureResolution = static_cast<glm::ivec2 *>(p_data);
-			m_renderToTextureResolution = *renderToTextureResolution;
-			m_renderTask->m_renderer.setRenderToTextureResolution(m_renderToTextureResolution);
+		case DataType::DataType_DeleteComponent:
+			{
+				// Get the world scene required for getting the entity registry and deleting components
+				WorldScene *worldScene = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World));
 
-			// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
-			if(p_deleteAfterReceiving)
-				delete renderToTextureResolution;
-		}
-		break;
+				// Get the entity registry 
+				auto &entityRegistry = worldScene->getEntityRegistry();
 
-	case DataType_Texture2D:
-		{
-			TextureLoader2D::Texture2DHandle *textureHandle = static_cast<TextureLoader2D::Texture2DHandle *>(p_data);
-			if(textureHandle->isLoadedToMemory())
-				m_sceneObjects.m_loadToVideoMemory.emplace_back(*textureHandle);
+				// Get entity and component data
+				auto const *componentData = static_cast<EntityAndComponent *>(p_data);
 
-			// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
-			if(p_deleteAfterReceiving)
-				delete textureHandle;
-		}
-		break;
+				// Delete the component based on its type
+				switch(componentData->m_componentType)
+				{
+					case ComponentType::ComponentType_CameraComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<CameraComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								worldScene->removeComponent<CameraComponent>(componentData->m_entityID);
+							}
+						}
+						break;
 
-	case DataType_Texture3D:
+					case ComponentType::ComponentType_LightComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<LightComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								worldScene->removeComponent<LightComponent>(componentData->m_entityID);
+							}
+						}
+						break;
 
-		break;
+					case ComponentType::ComponentType_ModelComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<ModelComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								worldScene->removeComponent<ModelComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+
+					case ComponentType::ComponentType_ShaderComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<ShaderComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								worldScene->removeComponent<ShaderComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+				}
+
+				// Delete the sent data if the ownership of it was transfered
+				if(p_deleteAfterReceiving)
+					delete componentData;
+			}
+			break;
+
+		case DataType::DataType_GUIPassFunctors:
+			m_renderTask->m_renderer.setGUIPassFunctorSequence(static_cast<FunctorSequence *>(p_data));
+			break;
+
+		case DataType::DataType_RenderToTexture:
+			m_renderToTexture = static_cast<bool>(p_data);
+			m_renderTask->m_renderer.setRenderFinalToTexture(m_renderToTexture);
+			break;
+
+		case DataType::DataType_RenderToTextureResolution:
+			{
+				auto renderToTextureResolution = static_cast<glm::ivec2 *>(p_data);
+				m_renderToTextureResolution = *renderToTextureResolution;
+				m_renderTask->m_renderer.setRenderToTextureResolution(m_renderToTextureResolution);
+
+				// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
+				if(p_deleteAfterReceiving)
+					delete renderToTextureResolution;
+			}
+			break;
+
+		case DataType::DataType_Texture2D:
+			{
+				TextureLoader2D::Texture2DHandle *textureHandle = static_cast<TextureLoader2D::Texture2DHandle *>(p_data);
+				if(textureHandle->isLoadedToMemory())
+					m_sceneObjects.m_loadToVideoMemory.emplace_back(*textureHandle);
+
+				// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
+				if(p_deleteAfterReceiving)
+					delete textureHandle;
+			}
+			break;
+
+		case DataType::DataType_Texture3D:
+
+			break;
 	}
 }
 
@@ -783,6 +884,16 @@ const unsigned int RendererScene::getUnsignedInt(const Observer *p_observer, Bit
 	return NullObjects::NullUnsignedInt;
 }
 
+const glm::mat4 &RendererScene::getViewMatrix() const
+{
+	return static_cast<RendererSystem *>(m_system)->getRenderer().getFrameData().m_viewMatrix;
+}
+
+const glm::mat4 &RendererScene::getProjectionMatrix() const
+{
+	return static_cast<RendererSystem *>(m_system)->getRenderer().getFrameData().m_projMatrix;
+}
+
 MaterialData RendererScene::loadMaterialData(PropertySet &p_materialProperty, Model::MaterialArrays &p_materialArraysFromModel, MaterialType p_materialType, std::size_t p_meshIndex)
 {
 	// Declare the material data that is to be returned and a flag showing whether the material data was loaded successfully

+ 5 - 0
Praxis3D/Source/RendererScene.h

@@ -123,6 +123,9 @@ public:
 	// Processes all the objects and puts them in the separate vectors
 	void update(const float p_deltaTime);
 
+	// Get all the created components of the given entity that belong to this scene
+	std::vector<SystemObject *> getComponents(const EntityID p_entityID);
+
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true);
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const GraphicsComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true)
 	{
@@ -214,6 +217,8 @@ public:
 	Systems::TypeID getSystemType() { return Systems::Graphics; }
 	inline SceneObjects &getSceneObjects() { return m_sceneObjects; }
 	const inline RenderingPasses &getRenderingPasses() const { return m_renderingPasses; }
+	const glm::mat4 &getViewMatrix() const;
+	const glm::mat4 &getProjectionMatrix() const;
 
 	static void exportRenderingPasses(PropertySet &p_propertySet, const RenderingPasses &p_renderingPasses)
 	{

+ 6 - 0
Praxis3D/Source/RigidBodyComponent.cpp

@@ -9,6 +9,12 @@ void RigidBodyComponent::changeOccurred(ObservedSubject *p_subject, BitMask p_ch
 	// Track whether any spatial data was modified, so that the transform matrix can be recreated
 	bool transformModifed = false;
 
+	if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+	{
+		// Get the active flag from the subject and set the active flag accordingly
+		setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+	}
+
 	// Consider ignoring LocalTransform change, as Bullet can only accept a transform matrix that does not have scale applied to it. LocalTransform however includes scaling.
 	// To avoid scaled transform, only the position is retrieved from the LocalTransform, and the rotation is retrieved by getting a LocalRotation quaternion.
 	// This might cause a problem of getting an out-of-date rotation, as it is not certain if the LocalRotation quaternion has been updated.

+ 34 - 0
Praxis3D/Source/RigidBodyComponent.h

@@ -50,6 +50,40 @@ public:
 	}
 	~RigidBodyComponent()
 	{
+		switch(m_collisionShapeType)
+		{
+			case RigidBodyComponent::CollisionShapeType_Box:
+				if(m_collisionShape.m_boxShape != nullptr)
+					delete m_collisionShape.m_boxShape;
+				break;
+			case RigidBodyComponent::CollisionShapeType_Capsule:
+				if(m_collisionShape.m_capsuleShape != nullptr)
+					delete m_collisionShape.m_capsuleShape;
+				break;
+			case RigidBodyComponent::CollisionShapeType_Cone:
+				if(m_collisionShape.m_coneShape != nullptr)
+					delete m_collisionShape.m_coneShape;
+				break;
+			case RigidBodyComponent::CollisionShapeType_ConvexHull:
+				if(m_collisionShape.m_convexHullShape != nullptr)
+					delete m_collisionShape.m_convexHullShape;
+				break;
+			case RigidBodyComponent::CollisionShapeType_Cylinder:
+				if(m_collisionShape.m_cylinderShape != nullptr)
+					delete m_collisionShape.m_cylinderShape;
+				break;
+			case RigidBodyComponent::CollisionShapeType_Sphere:
+				if(m_collisionShape.m_sphereShape != nullptr)
+					delete m_collisionShape.m_sphereShape;
+				break;
+		}
+
+		if(m_rigidBody != nullptr)
+			delete m_rigidBody;
+
+		if(m_constructionInfo != nullptr)
+			delete m_constructionInfo;
+
 	}
 	ErrorCode init() final override
 	{

+ 47 - 0
Praxis3D/Source/ScriptScene.cpp

@@ -129,6 +129,20 @@ ErrorCode ScriptScene::preload()
 	return ErrorCode::Success;
 }
 
+std::vector<SystemObject *> ScriptScene::getComponents(const EntityID p_entityID)
+{
+	std::vector<SystemObject *> returnVector;
+
+	// Get the entity registry 
+	auto &entityRegistry = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World))->getEntityRegistry();
+
+	auto *luaComponent = entityRegistry.try_get<LuaComponent>(p_entityID);
+	if(luaComponent != nullptr)
+		returnVector.push_back(luaComponent);
+
+	return returnVector;
+}
+
 std::vector<SystemObject*> ScriptScene::createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading)
 {
 	return createComponents(p_entityID, p_constructionInfo.m_scriptComponents, p_startLoading);
@@ -240,6 +254,39 @@ void ScriptScene::receiveData(const DataType p_dataType, void *p_data, const boo
 {
 	switch(p_dataType)
 	{
+		case DataType::DataType_DeleteComponent:
+			{
+				// Get the world scene required for getting the entity registry and deleting components
+				WorldScene *worldScene = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World));
+
+				// Get the entity registry 
+				auto &entityRegistry = worldScene->getEntityRegistry();
+
+				// Get entity and component data
+				auto const *componentData = static_cast<EntityAndComponent *>(p_data);
+
+				// Delete the component based on its type
+				switch(componentData->m_componentType)
+				{
+					case ComponentType::ComponentType_LuaComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<LuaComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								worldScene->removeComponent<LuaComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+				}
+
+				// Delete the sent data if the ownership of it was transfered
+				if(p_deleteAfterReceiving)
+					delete componentData;
+			}
+			break;
+
 	case DataType_EnableLuaScripting:
 		m_luaScriptsEnabled = static_cast<bool>(p_data);
 		break;

+ 3 - 0
Praxis3D/Source/ScriptScene.h

@@ -56,6 +56,9 @@ public:
 
 	ErrorCode preload();
 
+	// Get all the created components of the given entity that belong to this scene
+	std::vector<SystemObject *> getComponents(const EntityID p_entityID);
+
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true);
 	std::vector<SystemObject*> createComponents(const EntityID p_entityID, const ScriptComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true)
 	{

+ 7 - 3
Praxis3D/Source/ShaderComponent.h

@@ -20,8 +20,8 @@ public:
 		std::string m_fragmentShaderFilename;
 	};
 
-	ShaderComponent(SystemScene *p_systemScene, std::string p_name, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::Shaders, p_entityID), m_shaderData(nullptr) { }
-	ShaderComponent(SystemScene *p_systemScene, std::string p_name, ShaderLoader::ShaderProgram &p_shader, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::Shaders, p_entityID), m_shaderData(new ShaderData(p_shader)) { }
+	ShaderComponent(SystemScene *p_systemScene, std::string p_name, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::ShaderComponent, p_entityID), m_shaderData(nullptr) { }
+	ShaderComponent(SystemScene *p_systemScene, std::string p_name, ShaderLoader::ShaderProgram &p_shader, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::ShaderComponent, p_entityID), m_shaderData(new ShaderData(p_shader)) { }
 	~ShaderComponent() 
 	{ 
 		delete m_shaderData;
@@ -39,7 +39,11 @@ public:
 
 	BitMask getPotentialSystemChanges() { return Systems::Changes::None; }
 
-	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType) { }
+	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType)
+	{
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+	}
 
 	ErrorCode importObject(const PropertySet &p_properties)
 	{ 

+ 6 - 0
Praxis3D/Source/SoundComponent.h

@@ -89,6 +89,12 @@ public:
 
 	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType)
 	{
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+		{
+			// Get the active flag from the subject and set the active flag accordingly
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+		}
+
 		if(CheckBitmask(p_changeType, Systems::Changes::Audio::Filename))
 		{
 			m_soundFilename = p_subject->getString(this, Systems::Changes::Audio::Filename);

+ 3 - 0
Praxis3D/Source/SoundListenerComponent.h

@@ -49,6 +49,9 @@ public:
 
 	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType)
 	{
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+
 		if(CheckBitmask(p_changeType, Systems::Changes::Audio::ListenerID))
 			m_listenerID = p_subject->getInt(this, Systems::Changes::Audio::ListenerID);
 	}

+ 6 - 0
Praxis3D/Source/SpatialComponent.h

@@ -74,6 +74,12 @@ public:
 		// Track what data has been modified
 		BitMask newChanges = Systems::Changes::None;
 
+		if(CheckBitmask(p_changeType, Systems::Changes::Generic::Active))
+		{
+			// Get the active flag from the subject and set the active flag accordingly
+			setActive(p_subject->getBool(this, Systems::Changes::Generic::Active));
+		}
+
 		// Process the spatial changes and record the world-space changes
 		/*newChanges = */m_spatialData.changeOccurred(*p_subject, p_changeType & Systems::Changes::Spatial::All);
 

+ 5 - 0
Praxis3D/Source/System.cpp

@@ -21,6 +21,11 @@ SystemObject::~SystemObject()
 	ObjectDirectory::unregisterObject(*this);
 }
 
+std::vector<SystemObject*> SystemScene::getComponents(const EntityID p_entityID)
+{
+	return std::vector<SystemObject *>();
+}
+
 std::vector<SystemObject*> SystemScene::createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading)
 {
 	return std::vector<SystemObject*>();

+ 3 - 0
Praxis3D/Source/System.h

@@ -92,6 +92,9 @@ public:
 	// Start loading all the scene objects in the background threads and return (without waiting)
 	virtual void loadInBackground() = 0;
 
+	// Get all the created components of the given entity that belong to this scene
+	virtual std::vector<SystemObject*> getComponents(const EntityID p_entityID);
+
 	// Create all the components that belong to this scene, that are contained in ComponentsConstructionInfo; return a vector of all created components
 	virtual std::vector<SystemObject*> createComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true);
 

+ 1 - 1
Praxis3D/Source/UniformData.h

@@ -29,7 +29,7 @@ struct UniformFrameData
 				m_atmScatProjMatrix,
 				m_transposeViewMatrix;
 
-	// Parameters of direction light, since there can be only one of it
+	// Parameters of directional light, since there can be only one of it
 	DirectionalLightDataSet m_directionalLight;
 
 	// Delta time of the last frame

+ 6 - 2
Praxis3D/Source/Window.cpp

@@ -14,8 +14,12 @@ Window::Window()
 
 	m_numDisplays = 0;
 
+	m_mousePositionBeforeSetRelativeX = 0;
+	m_mousePositionBeforeSetRelativeY = 0;
+
 	m_SDLWindow = nullptr;
-	m_SDLWindow = nullptr;
+	m_GLContext = nullptr;
+	m_guiHandler = nullptr;
 
 	// Reserve the space, since we know the number of supported scancodes
 	m_scancodeNames.reserve(Scancode::NumberOfScancodes);
@@ -161,7 +165,7 @@ void Window::handleEvents()
 		else
 		{
 			// If GUI is enabled, send the event to it
-			if(m_enableGUI)
+			if(m_enableGUI && !Config::m_windowVar.mouse_captured)
 				m_guiHandler->processSDLEvent(SDLEvent);
 				
 			handleSDLEvent(SDLEvent);

+ 11 - 5
Praxis3D/Source/Window.h

@@ -300,12 +300,17 @@ public:
 	// Sets relative mouse mode (cursor becomes hidden and clipped inside the window)
 	void setMouseRelativeMode(const bool p_relativeMode)
 	{
+		// If the mouse is going into relative mode, save the current mouse position for later
+		// If the mouse is going out of relative mode, restore its position to what it was before going into relative mode
+		if(p_relativeMode)
+			SDL_GetMouseState(&m_mousePositionBeforeSetRelativeX, &m_mousePositionBeforeSetRelativeY);
+		else
+			SDL_WarpMouseInWindow(m_SDLWindow, m_mousePositionBeforeSetRelativeX, m_mousePositionBeforeSetRelativeY);
+
 		// SDL implementation of bool is "0" or "1", so we need to "convert" the regular bool
 		SDL_SetRelativeMouseMode(p_relativeMode ? SDL_TRUE : SDL_FALSE);
 
-		ErrHandlerLoc::get().log(ErrorType::Info, ErrorSource::Source_Window, p_relativeMode ?
-								 "Mouse captured"
-								 : "Mouse released");
+		//ErrHandlerLoc::get().log(ErrorType::Info, ErrorSource::Source_Window, p_relativeMode ? "Mouse captured" : "Mouse released");
 	}
 
 	// Sets the vertical synchronization
@@ -502,12 +507,13 @@ private:
 	}
 
 	int m_numDisplays;
-	//int m_screenWidth;
-	//int m_screenHeight;
 
 	bool m_mouseCapturedBeforeLostFocus;
 	bool m_inFullscreen;
 
+	int m_mousePositionBeforeSetRelativeX;
+	int m_mousePositionBeforeSetRelativeY;
+
 	// Handles to window and OpenGL contexts
 	SDL_Window *m_SDLWindow;
 	SDL_GLContext m_GLContext;

+ 237 - 0
Praxis3D/Source/WorldScene.cpp

@@ -56,7 +56,189 @@ void WorldScene::update(const float p_deltaTime)
 
 		component.update(p_deltaTime);
 	}
+}
+
+std::vector<SystemObject *> WorldScene::getComponents(const EntityID p_entityID)
+{
+	std::vector<SystemObject *> returnVector;
+
+	// Get the entity registry 
+	auto &entityRegistry = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World))->getEntityRegistry();
+
+	auto *metadataComponent = m_entityRegistry.try_get<MetadataComponent>(p_entityID);
+	if(metadataComponent != nullptr)
+		returnVector.push_back(metadataComponent);
+
+	auto *spatialComponent = m_entityRegistry.try_get<SpatialComponent>(p_entityID);
+	if(spatialComponent != nullptr)
+		returnVector.push_back(spatialComponent);
+
+	auto *objectMaterialComponent = m_entityRegistry.try_get<ObjectMaterialComponent>(p_entityID);
+	if(objectMaterialComponent != nullptr)
+		returnVector.push_back(objectMaterialComponent);
 
+	return returnVector;
+}
+
+ErrorCode WorldScene::addComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading)
+{
+	ErrorCode returnError = ErrorCode::Success;
+
+	if(m_entityRegistry.valid(p_entityID))
+	{
+		// Add WORLD components
+		std::vector<SystemObject *> worldComponents = createComponents(p_entityID, p_constructionInfo.m_worldComponents, p_startLoading);
+
+		SystemObject *newSpatialComponent = nullptr;
+		SystemObject *oldSpatialComponent = nullptr;
+
+		// Find a spatial component within the world components array
+		for(decltype(worldComponents.size()) i = 0, size = worldComponents.size(); i < size; i++)
+			if(worldComponents[i]->getObjectType() == Properties::SpatialComponent)
+			{
+				newSpatialComponent = worldComponents[i];
+				break;
+			}
+
+		if(newSpatialComponent != nullptr)
+			oldSpatialComponent = m_entityRegistry.try_get<MetadataComponent>(p_entityID);
+
+		// Add AUDIO components
+		std::vector<SystemObject *> newAudioComponents = m_sceneLoader->getSystemScene(Systems::Audio)->createComponents(p_entityID, p_constructionInfo, p_startLoading);
+		std::vector<SystemObject *> oldAudioComponents = m_sceneLoader->getSystemScene(Systems::Audio)->getComponents(p_entityID);
+
+		// Add RENDERING components
+		std::vector<SystemObject *> newRenderingComponents = m_sceneLoader->getSystemScene(Systems::Graphics)->createComponents(p_entityID, p_constructionInfo, p_startLoading);
+		std::vector<SystemObject *> oldRenderingComponents = m_sceneLoader->getSystemScene(Systems::Graphics)->getComponents(p_entityID);
+
+		// Add GUI components
+		std::vector<SystemObject *> newGuiComponents = m_sceneLoader->getSystemScene(Systems::GUI)->createComponents(p_entityID, p_constructionInfo, p_startLoading);
+		std::vector<SystemObject *> oldGuiComponents = m_sceneLoader->getSystemScene(Systems::GUI)->getComponents(p_entityID);
+
+		// Add PHYSICS components
+		std::vector<SystemObject *> newPhysicsComponents = m_sceneLoader->getSystemScene(Systems::Physics)->createComponents(p_entityID, p_constructionInfo, p_startLoading);
+		std::vector<SystemObject *> oldPhysicsComponents = m_sceneLoader->getSystemScene(Systems::Physics)->getComponents(p_entityID);
+
+		// Add SCRIPTING components
+		std::vector<SystemObject *> newScriptingComponents = m_sceneLoader->getSystemScene(Systems::Script)->createComponents(p_entityID, p_constructionInfo, p_startLoading);
+		std::vector<SystemObject *> oldScriptingComponents = m_sceneLoader->getSystemScene(Systems::Script)->getComponents(p_entityID);
+
+		// Link subjects and observers of different components
+		// Link NEW components to both NEW and OLD components
+		// Link OLD components to NEW components only (to avoid duplicate linking)
+
+		if(newSpatialComponent != nullptr)
+		{
+			// Link PHYSICS -> SPATIAL
+			// NEW components -> NEW and OLD
+			for(decltype(newPhysicsComponents.size()) i = 0, size = newPhysicsComponents.size(); i < size; i++)
+			{
+				m_sceneLoader->getChangeController()->createObjectLink(newPhysicsComponents[i], newSpatialComponent);
+			}
+			for(decltype(oldScriptingComponents.size()) i = 0, size = oldScriptingComponents.size(); i < size; i++)
+			{
+				m_sceneLoader->getChangeController()->createObjectLink(oldScriptingComponents[i], newSpatialComponent);
+			}
+		}
+		else
+			if(oldSpatialComponent != nullptr)
+			{
+				// Link PHYSICS -> SPATIAL
+				// OLD components -> NEW
+				for(decltype(newPhysicsComponents.size()) i = 0, size = newPhysicsComponents.size(); i < size; i++)
+				{
+					m_sceneLoader->getChangeController()->createObjectLink(newPhysicsComponents[i], oldSpatialComponent);
+				}
+			}
+
+
+		// Link SCRIPTING
+		// NEW components -> NEW and OLD
+		for(decltype(newScriptingComponents.size()) scriptingIndex = 0, scriptingSize = newScriptingComponents.size(); scriptingIndex < scriptingSize; scriptingIndex++)
+		{
+			// If there are no physics components, link to spatial directly. If there are physics components, link to physics components instead
+			if(newPhysicsComponents.empty())
+			{
+				// Link SCRIPTING -> SPATIAL
+				// NEW -> NEW
+				if(newSpatialComponent != nullptr)
+					m_sceneLoader->getChangeController()->createObjectLink(newScriptingComponents[scriptingIndex], newSpatialComponent);
+				else
+					// NEW -> OLD
+					if(oldSpatialComponent != nullptr)
+						m_sceneLoader->getChangeController()->createObjectLink(newScriptingComponents[scriptingIndex], oldSpatialComponent);
+			}
+			else
+			{
+				// Link SCRIPTING -> PHYSICS
+				// NEW -> NEW
+				for(decltype(newPhysicsComponents.size()) physicsIndex = 0, physicsSize = newPhysicsComponents.size(); physicsIndex < physicsSize; physicsIndex++)
+					m_sceneLoader->getChangeController()->createObjectLink(newScriptingComponents[scriptingIndex], newPhysicsComponents[physicsIndex]);
+				// NEW -> OLD
+				for(decltype(oldPhysicsComponents.size()) physicsIndex = 0, physicsSize = oldPhysicsComponents.size(); physicsIndex < physicsSize; physicsIndex++)
+					m_sceneLoader->getChangeController()->createObjectLink(newScriptingComponents[scriptingIndex], oldPhysicsComponents[physicsIndex]);
+			}
+
+			// Link SCRIPTING -> AUDIO
+			// NEW -> NEW
+			for(decltype(newAudioComponents.size()) audioIndex = 0, audioSize = newAudioComponents.size(); audioIndex < audioSize; audioIndex++)
+			{
+				m_sceneLoader->getChangeController()->createObjectLink(newScriptingComponents[scriptingIndex], newAudioComponents[audioIndex]);
+			}
+			// NEW -> OLD
+			for(decltype(oldAudioComponents.size()) audioIndex = 0, audioSize = oldAudioComponents.size(); audioIndex < audioSize; audioIndex++)
+			{
+				m_sceneLoader->getChangeController()->createObjectLink(newScriptingComponents[scriptingIndex], oldAudioComponents[audioIndex]);
+			}
+
+			// Link SCRIPTING -> GUI
+			// NEW -> NEW
+			for(decltype(newGuiComponents.size()) guiIndex = 0, guiSize = newGuiComponents.size(); guiIndex < guiSize; guiIndex++)
+			{
+				m_sceneLoader->getChangeController()->createObjectLink(newScriptingComponents[scriptingIndex], newGuiComponents[guiIndex]);
+			}
+			// NEW -> OLD
+			for(decltype(oldGuiComponents.size()) guiIndex = 0, guiSize = oldGuiComponents.size(); guiIndex < guiSize; guiIndex++)
+			{
+				m_sceneLoader->getChangeController()->createObjectLink(newScriptingComponents[scriptingIndex], oldGuiComponents[guiIndex]);
+			}
+		}
+
+		// Link SCRIPTING
+		// OLD components -> NEW
+		for(decltype(oldScriptingComponents.size()) scriptingIndex = 0, scriptingSize = oldScriptingComponents.size(); scriptingIndex < scriptingSize; scriptingIndex++)
+		{
+			// If there are no physics components, link to spatial directly. If there are physics components, link to physics components instead
+			if(newPhysicsComponents.empty())
+			{
+				// Link SCRIPTING -> SPATIAL
+				if(newSpatialComponent != nullptr)
+					m_sceneLoader->getChangeController()->createObjectLink(oldScriptingComponents[scriptingIndex], newSpatialComponent);
+			}
+			else
+			{
+				// Link SCRIPTING -> PHYSICS
+				for(decltype(newPhysicsComponents.size()) physicsIndex = 0, physicsSize = newPhysicsComponents.size(); physicsIndex < physicsSize; physicsIndex++)
+					m_sceneLoader->getChangeController()->createObjectLink(oldScriptingComponents[scriptingIndex], newPhysicsComponents[physicsIndex]);
+			}
+
+			// Link SCRIPTING -> AUDIO
+			for(decltype(newAudioComponents.size()) audioIndex = 0, audioSize = newAudioComponents.size(); audioIndex < audioSize; audioIndex++)
+			{
+				m_sceneLoader->getChangeController()->createObjectLink(oldScriptingComponents[scriptingIndex], newAudioComponents[audioIndex]);
+			}
+
+			// Link SCRIPTING -> GUI
+			for(decltype(newGuiComponents.size()) guiIndex = 0, guiSize = newGuiComponents.size(); guiIndex < guiSize; guiIndex++)
+			{
+				m_sceneLoader->getChangeController()->createObjectLink(oldScriptingComponents[scriptingIndex], newGuiComponents[guiIndex]);
+			}
+		}
+	}
+	else
+		returnError = ErrorCode::Nonexistent_object_id;
+
+	return returnError;
 }
 
 EntityID WorldScene::createEntity(const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading)
@@ -274,3 +456,58 @@ SystemObject *WorldScene::createComponent(const EntityID p_entityID, const Compo
 
 	return metadataComponent;
 }
+
+void WorldScene::changeOccurred(ObservedSubject *p_subject, BitMask p_changeType)
+{
+}
+
+void WorldScene::receiveData(const DataType p_dataType, void *p_data, const bool p_deleteAfterReceiving)
+{
+	switch(p_dataType)
+	{
+		case DataType::DataType_DeleteComponent:
+			{
+				// Get the world scene required for getting the entity registry and deleting components
+				WorldScene *worldScene = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World));
+
+				// Get the entity registry 
+				auto &entityRegistry = worldScene->getEntityRegistry();
+
+				// Get entity and component data
+				auto const *componentData = static_cast<EntityAndComponent *>(p_data);
+
+				// Delete the component based on its type
+				switch(componentData->m_componentType)
+				{
+					case ComponentType::ComponentType_ObjectMaterialComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<ObjectMaterialComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								worldScene->removeComponent<ObjectMaterialComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+
+					case ComponentType::ComponentType_SpatialComponent:
+						{
+							// Check if the component exists
+							auto *component = entityRegistry.try_get<SpatialComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								worldScene->removeComponent<SpatialComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+				}
+
+				// Delete the sent data if the ownership of it was transfered
+				if(p_deleteAfterReceiving)
+					delete componentData;
+			}
+			break;
+	}
+}

+ 8 - 1
Praxis3D/Source/WorldScene.h

@@ -59,6 +59,11 @@ public:
 
 	void loadInBackground() { }
 
+	// Get all the created components of the given entity that belong to this scene
+	std::vector<SystemObject *> getComponents(const EntityID p_entityID);
+
+	// Add a components to an existing entity. Fails if the entity doesn't exist
+	ErrorCode addComponents(const EntityID p_entityID, const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true);
 	EntityID createEntity(const ComponentsConstructionInfo &p_constructionInfo, const bool p_startLoading = true);
 	void exportEntity(const EntityID p_entityID, ComponentsConstructionInfo &p_constructionInfo);
 
@@ -129,7 +134,9 @@ public:
 		p_constructionInfo.m_localScale = p_component.getSpatialDataChangeManager().getLocalSpaceData().m_spatialData.m_scale;
 	}
 
-	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType) { }
+	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType);
+
+	void receiveData(const DataType p_dataType, void *p_data, const bool p_deleteAfterReceiving);
 
 	SystemTask *getSystemTask() { return m_worldTask; };
 	Systems::TypeID getSystemType() { return Systems::TypeID::World; };

+ 37 - 1
Praxis3D/imgui.ini

@@ -4,7 +4,7 @@ Size=400,400
 Collapsed=0
 
 [Window][Dear ImGui Demo]
-Pos=37,390
+Pos=49,544
 Size=461,545
 Collapsed=1
 
@@ -828,6 +828,42 @@ Column 3  Sort=0^
 RefScale=13
 Column 0  Sort=0v
 
+[Table][0x6BB87724,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xD804F7B1,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x1CF7BAE3,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x55E15B96,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x4821F9FA,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x5EB19816,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xB7F451A0,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x944DA7B0,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x9EF9B7C0,4]
+RefScale=13
+Column 0  Sort=0v
+
 [Docking][Data]
 DockSpace         ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,69 Size=1920,1011 Split=X
   DockNode        ID=0x00000007 Parent=0x8B93E3BD SizeRef=1409,1011 Split=Y