// The MIT License(MIT) // // Copyright(c) 2016 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. #include "imgui.h" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif #include "imgui_internal.h" #include "ImGuizmo.h" // includes patches for multiview from // https://github.com/CedricGuillemet/ImGuizmo/issues/15 namespace ImGuizmo { 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; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 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]; } //template T LERP(T x, T y, float z) { return (x + (y - x)*z); } template T Clamp(T x, T y, T z) { return ((xz) ? z : x)); } template T max(T x, T y) { return (x > y) ? x : y; } template T min(T x, T y) { return (x < y) ? x : y; } template 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()); 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]; } }; 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 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]; }; matrix_t(const matrix_t& other) { memcpy(&m16[0], &other.m16[0], sizeof(float) * 16); } matrix_t() {} 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 { NONE, MOVE_X, MOVE_Y, MOVE_Z, MOVE_XY, MOVE_XZ, MOVE_YZ, MOVE_SCREEN, ROTATE_X, ROTATE_Y, ROTATE_Z, ROTATE_SCREEN, SCALE_X, SCALE_Y, SCALE_Z, SCALE_XYZ, BOUNDS }; struct Context { Context() : mbUsing(false), mbEnable(true), mbUsingBounds(false) { } ImDrawList* mDrawList; MODE mMode; matrix_t mViewMat; matrix_t mProjectionMat; matrix_t mModel; matrix_t mModelInverse; matrix_t mModelSource; matrix_t mModelSourceInverse; matrix_t mMVP; 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; // translation vec_t mTranslationPlan; vec_t mTranslationPlanOrigin; vec_t mMatrixOrigin; // rotation vec_t mRotationVectorSource; float mRotationAngle; float mRotationAngleOrigin; //vec_t mWorldToLocalAxis; // scale vec_t mScale; vec_t mScaleValueOrigin; float mSaveMousePosx; // save axis factor when using gizmo bool mBelowAxisLimit[3]; bool mBelowPlaneLimit[3]; float mAxisFactor[3]; // 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; }; static Context gContext; static const float angleLimit = 0.96f; static const float planeLimit = 0.2f; 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 ImU32 directionColor[3] = { 0xFF0000AA, 0xFF00AA00, 0xFFAA0000 }; // Alpha: 100%: FF, 87%: DE, 70%: B3, 54%: 8A, 50%: 80, 38%: 61, 12%: 1F static const ImU32 planeBorderColor[3] = { 0xFFAA0000, 0xFF0000AA, 0xFF00AA00 }; static const ImU32 planeColor[3] = { 0x610000AA, 0x6100AA00, 0x61AA0000 }; static const ImU32 selectionColor = 0x8A1080FF; static const ImU32 inactiveColor = 0x99999999; static const ImU32 translationLineColor = 0xAAAAAAAA; static const char *translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", "X : %5.3f Y : %5.3f", "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %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, 0,1,0, 1,2,0, 0,2,1, 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(vec_t *gizmoHitProportion); static int GetRotateType(); static int GetScaleType(); static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat) { 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 *= gContext.mWidth; trans.y *= gContext.mHeight; trans.x += gContext.mX; trans.y += gContext.mY; return ImVec2(trans.x, trans.y); } static void ComputeCameraRay(vec_t &rayOrigin, vec_t &rayDir) { ImGuiIO& io = ImGui::GetIO(); matrix_t mViewProjInverse; mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); float mox = ((io.MousePos.x - gContext.mX) / gContext.mWidth) * 2.f - 1.f; float moy = (1.f - ((io.MousePos.y - gContext.mY) / gContext.mHeight)) * 2.f - 1.f; rayOrigin.Transform(makeVect(mox, moy, 0.f, 1.f), mViewProjInverse); rayOrigin *= 1.f / rayOrigin.w; vec_t rayEnd; rayEnd.Transform(makeVect(mox, moy, 1.f, 1.f), mViewProjInverse); rayEnd *= 1.f / rayEnd.w; rayDir = Normalized(rayEnd - rayOrigin); } static float IntersectRayPlane(const vec_t & rOrigin, const vec_t& rVector, const vec_t& plan) { float numer = plan.Dot3(rOrigin) - plan.w; float denom = plan.Dot3(rVector); if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect return -1.0f; return -(numer / denom); } static bool IsInContextRect( ImVec2 p ) { return IsWithin( p.x, gContext.mX, gContext.mXMax ) && IsWithin(p.y, gContext.mY, gContext.mYMax ); } 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; } void SetDrawlist() { gContext.mDrawList = ImGui::GetWindowDrawList(); } void BeginFrame() { ImGuiIO& io = ImGui::GetIO(); const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; ImGui::SetNextWindowSize(io.DisplaySize); ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); ImGui::Begin("gizmo", NULL, flags); gContext.mDrawList = ImGui::GetWindowDrawList(); ImGui::End(); ImGui::PopStyleColor(); } bool IsUsing() { return gContext.mbUsing||gContext.mbUsingBounds; } bool IsOver() { return (GetMoveType(NULL) != NONE) || GetRotateType() != NONE || GetScaleType() != NONE || IsUsing(); } void Enable(bool enable) { gContext.mbEnable = enable; if (!enable) { gContext.mbUsing = false; gContext.mbUsingBounds = false; } } static float GetUniform(const vec_t& position, const matrix_t& mat) { vec_t trf = makeVect(position.x, position.y, position.z, 1.f); trf.Transform(mat); return trf.w; } 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; if (mode == LOCAL) { gContext.mModel = *(matrix_t*)matrix; gContext.mModel.OrthoNormalize(); } 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; 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; gContext.mScreenFactor = 0.1f * GetUniform(gContext.mModel.v.position, gContext.mViewProjection); 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) { switch (operation) { case TRANSLATE: colors[0] = (type == MOVE_SCREEN) ? selectionColor : 0xFFFFFFFF; for (int i = 0; i < 3; i++) { int colorPlaneIndex = (i + 2) % 3; colors[i + 1] = (type == (int)(MOVE_X + i)) ? selectionColor : directionColor[i]; colors[i + 4] = (type == (int)(MOVE_XY + i)) ? selectionColor : planeColor[colorPlaneIndex]; colors[i + 4] = (type == MOVE_SCREEN) ? selectionColor : colors[i + 4]; } break; case ROTATE: colors[0] = (type == ROTATE_SCREEN) ? selectionColor : 0xFFFFFFFF; for (int i = 0; i < 3; i++) colors[i + 1] = (type == (int)(ROTATE_X + i)) ? selectionColor : directionColor[i]; break; case SCALE: colors[0] = (type == SCALE_XYZ) ? selectionColor : 0xFFFFFFFF; for (int i = 0; i < 3; i++) colors[i + 1] = (type == (int)(SCALE_X + i)) ? selectionColor : directionColor[i]; break; } } else { for (int i = 0; i < 7; i++) colors[i] = inactiveColor; } } static void ComputeTripodAxisAndVisibility(int axisIndex, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit) { const int planNormal = (axisIndex + 2) % 3; dirPlaneX = directionUnary[axisIndex]; dirPlaneY = directionUnary[(axisIndex + 1) % 3]; if (gContext.mbUsing) { // when using, use stored factors so the gizmo doesn't flip when we translate belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; dirPlaneX *= gContext.mAxisFactor[axisIndex]; dirPlaneY *= gContext.mAxisFactor[(axisIndex + 1) % 3]; } else { vec_t dirPlaneNormalWorld; dirPlaneNormalWorld.TransformVector(directionUnary[planNormal], gContext.mModel); dirPlaneNormalWorld.Normalize(); vec_t dirPlaneXWorld(dirPlaneX); dirPlaneXWorld.TransformVector(gContext.mModel); dirPlaneXWorld.Normalize(); vec_t dirPlaneYWorld(dirPlaneY); dirPlaneYWorld.TransformVector(gContext.mModel); dirPlaneYWorld.Normalize(); vec_t cameraEyeToGizmo = Normalized(gContext.mModel.v.position - gContext.mCameraEye); float dotCameraDirX = cameraEyeToGizmo.Dot3(dirPlaneXWorld); float dotCameraDirY = cameraEyeToGizmo.Dot3(dirPlaneYWorld); // compute factor values #ifdef FLIP float mulAxisX = (dotCameraDirX > 0.f) ? -1.f : 1.f; float mulAxisY = (dotCameraDirY > 0.f) ? -1.f : 1.f; #else float mulAxisX = 1.f; float mulAxisY = 1.f; #endif dirPlaneX *= mulAxisX; dirPlaneY *= mulAxisY; belowAxisLimit = fabsf(dotCameraDirX) < angleLimit; belowPlaneLimit = (fabsf(cameraEyeToGizmo.Dot3(dirPlaneNormalWorld)) > planeLimit); // and store values gContext.mAxisFactor[axisIndex] = mulAxisX; gContext.mAxisFactor[(axisIndex+1)%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, 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), -0.9999f, 0.9999f); float angle = acosf(acosAngle); angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; return angle; } static void DrawRotationGizmo(int type) { ImDrawList* drawList = gContext.mDrawList; // colors ImU32 colors[7]; ComputeColors(colors, type, ROTATE); vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); cameraToModelNormalized.TransformVector(gContext.mModelInverse); gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; for (int axis = 0; axis < 3; axis++) { ImVec2 circlePos[halfCircleSegmentCount]; float angleStart = atan2f(cameraToModelNormalized[(4-axis)%3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; for (unsigned int i = 0; i < halfCircleSegmentCount; i++) { float ng = angleStart + ZPI * ((float)i / (float)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; circlePos[i] = worldToPos(pos, gContext.mMVP); } float radiusAxis = sqrtf( (ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]) )); if(radiusAxis > gContext.mRadiusSquareCenter) gContext.mRadiusSquareCenter = radiusAxis; drawList->AddPolyline(circlePos, halfCircleSegmentCount, colors[3 - axis], false, 2); } drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, 3.f); if (gContext.mbUsing) { ImVec2 circlePos[halfCircleSegmentCount +1]; circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); for (unsigned int i = 1; i < halfCircleSegmentCount; 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; circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); } drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount, 0x801080FF); drawList->AddPolyline(circlePos, halfCircleSegmentCount, 0xFF1080FF, true, 2); ImVec2 destinationPosOnScreen = circlePos[1]; char tmps[512]; ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - ROTATE_X], (gContext.mRotationAngle/ZPI)*180.f, gContext.mRotationAngle); drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), 0xFF000000, tmps); drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), 0xFFFFFFFF, tmps); } } static void DrawHatchedAxis(const vec_t& axis) { 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, 0x80000000, 6.f); } } static void DrawScaleGizmo(int type) { ImDrawList* drawList = gContext.mDrawList; // colors ImU32 colors[7]; ComputeColors(colors, type, SCALE); // draw vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; if (gContext.mbUsing) scaleDisplay = gContext.mScale; for (unsigned int i = 0; i < 3; i++) { vec_t dirPlaneX, dirPlaneY; bool belowAxisLimit, belowPlaneLimit; ComputeTripodAxisAndVisibility(i, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); // draw axis if (belowAxisLimit) { ImVec2 baseSSpace = worldToPos(dirPlaneX * 0.1f * gContext.mScreenFactor, gContext.mMVP); ImVec2 worldDirSSpaceNoScale = worldToPos(dirPlaneX * gContext.mScreenFactor, gContext.mMVP); ImVec2 worldDirSSpace = worldToPos((dirPlaneX * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); if (gContext.mbUsing) { drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, 0xFF404040, 3.f); drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, 0xFF404040); } drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); drawList->AddCircleFilled(worldDirSSpace, 6.f, colors[i + 1]); #ifdef HATCHED if (gContext.mAxisFactor[i] < 0.f) DrawHatchedAxis(dirPlaneX * scaleDisplay[i]); #endif } } // draw screen cirle drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); if (gContext.mbUsing) { //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 - SCALE_X) * 3; ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), 0xFF000000, tmps); drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), 0xFFFFFFFF, tmps); } } static void DrawTranslationGizmo(int type) { ImDrawList* drawList = gContext.mDrawList; if (!drawList) 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 (unsigned int i = 0; i < 3; ++i) { vec_t dirPlaneX, dirPlaneY; ComputeTripodAxisAndVisibility(i, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); // draw axis if (belowAxisLimit) { ImVec2 baseSSpace = worldToPos(dirPlaneX * 0.1f * gContext.mScreenFactor, gContext.mMVP); ImVec2 worldDirSSpace = worldToPos(dirPlaneX * gContext.mScreenFactor, gContext.mMVP); drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); // Arrow head begin ImVec2 dir(origin - worldDirSSpace); float d = sqrtf(ImLengthSqr(dir)); dir /= d; // Normalize dir *= 6.0f; 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 #ifdef HATCHED if (gContext.mAxisFactor[i] < 0.f) DrawHatchedAxis(dirPlaneX); #endif } // draw plane if (belowPlaneLimit) { 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, planeBorderColor[i], true, 1.0f); drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); } } drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); if (gContext.mbUsing) { 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 - MOVE_X) * 3; ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), 0xFF000000, tmps); drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), 0xFFFFFFFF, tmps); } } static bool CanActivate() { if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) return true; return false; } static void HandleAndDrawLocalBounds(float *bounds, matrix_t *matrix, float *snapValues) { 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 (unsigned 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 ? 0xFF000000 : 0x80000000; 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 ); float stepLength = 1.f / (float)stepCount; for (int j = 0; j < stepCount; j++) { 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, 0xAAAAAA + anchorAlpha, 3.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); unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (0xAAAAAA + anchorAlpha); unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (0xAAAAAA + anchorAlpha); drawList->AddCircleFilled(worldBound1, AnchorBigRadius, bigAnchorColor); drawList->AddCircleFilled(midBound, AnchorSmallRadius, 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.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.mBoundsMatrix = gContext.mModelSource; } } if (gContext.mbUsingBounds) { 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), 0xFF000000, tmps); drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), 0xFFFFFFFF, tmps); } if (!io.MouseDown[0]) gContext.mbUsingBounds = false; if( gContext.mbUsingBounds ) break; } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // static int GetScaleType() { ImGuiIO& io = ImGui::GetIO(); int type = 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) type = SCALE_XYZ; const vec_t direction[3] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; // compute for (unsigned int i = 0; i < 3 && type == NONE; i++) { vec_t dirPlaneX, dirPlaneY; bool belowAxisLimit, belowPlaneLimit; ComputeTripodAxisAndVisibility(i, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); dirPlaneX.TransformVector(gContext.mModel); dirPlaneY.TransformVector(gContext.mModel); const int planNormal = (i + 2) % 3; const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, direction[planNormal])); vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; 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 (belowAxisLimit && dy > -0.1f && dy < 0.1f && dx > 0.1f && dx < 1.f) type = SCALE_X + i; } return type; } static int GetRotateType() { ImGuiIO& io = ImGui::GetIO(); int type = 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 (dist >= (gContext.mRadiusSquareCenter - 1.0f) && dist < (gContext.mRadiusSquareCenter + 1.0f)) type = ROTATE_SCREEN; const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir}; for (unsigned int i = 0; i < 3 && type == NONE; i++) { // pickup plan vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; if (Dot(Normalized(localPos), gContext.mRayVector) > FLT_EPSILON) continue; float distance = localPos.Length() / gContext.mScreenFactor; if (distance > 0.9f && distance < 1.1f) type = ROTATE_X + i; } return type; } static int GetMoveType(vec_t *gizmoHitProportion) { ImGuiIO& io = ImGui::GetIO(); int type = 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) type = MOVE_SCREEN; const vec_t direction[3] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; // compute for (unsigned int i = 0; i < 3 && type == NONE; i++) { vec_t dirPlaneX, dirPlaneY; bool belowAxisLimit, belowPlaneLimit; ComputeTripodAxisAndVisibility(i, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); dirPlaneX.TransformVector(gContext.mModel); dirPlaneY.TransformVector(gContext.mModel); const int planNormal = (i + 2) % 3; const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, direction[planNormal])); vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; 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 (belowAxisLimit && dy > -0.1f && dy < 0.1f && dx > 0.1f && dx < 1.f) type = MOVE_X + i; if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3]) type = MOVE_XY + i; if (gizmoHitProportion) *gizmoHitProportion = makeVect(dx, dy, 0.f); } return type; } static void HandleTranslation(float *matrix, float *deltaMatrix, int& type, float *snap) { ImGuiIO& io = ImGui::GetIO(); bool applyRotationLocaly = gContext.mMode == LOCAL || type == MOVE_SCREEN; // move if (gContext.mbUsing) { ImGui::CaptureMouseFromApp(); const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; // compute delta vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; vec_t delta = newOrigin - gContext.mModel.v.position; // 1 axis constraint if (gContext.mCurrentOperation >= MOVE_X && gContext.mCurrentOperation <= MOVE_Z) { int axisIndex = gContext.mCurrentOperation - MOVE_X; const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; 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; } // compute matrix & delta matrix_t deltaMatrixTranslation; deltaMatrixTranslation.Translation(delta); if (deltaMatrix) memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); 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(&gizmoHitProportion); if (CanActivate() && type != NONE) { ImGui::CaptureMouseFromApp(); gContext.mbUsing = true; 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.right, gContext.mModel.v.up, -gContext.mCameraDir }; // pickup plan gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - 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); } } } static void HandleScale(float *matrix, float *deltaMatrix, int& type, float *snap) { ImGuiIO& io = ImGui::GetIO(); if (!gContext.mbUsing) { // find new possible way to scale type = GetScaleType(); if (CanActivate() && type != NONE) { ImGui::CaptureMouseFromApp(); gContext.mbUsing = true; 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 - 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) { ImGui::CaptureMouseFromApp(); 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.mModel.v.position; // 1 axis constraint if (gContext.mCurrentOperation >= SCALE_X && gContext.mCurrentOperation <= SCALE_Z) { int axisIndex = gContext.mCurrentOperation - SCALE_X; const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; float lengthOnAxis = Dot(axisValue, delta); delta = axisValue * lengthOnAxis; vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModel.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); // compute matrix & delta matrix_t deltaMatrixScale; deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); matrix_t res = deltaMatrixScale * gContext.mModel; *(matrix_t*)matrix = res; if (deltaMatrix) { deltaMatrixScale.Scale(gContext.mScale); memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); } if (!io.MouseDown[0]) gContext.mbUsing = false; type = gContext.mCurrentOperation; } } static void HandleRotation(float *matrix, float *deltaMatrix, int& type, float *snap) { ImGuiIO& io = ImGui::GetIO(); bool applyRotationLocaly = gContext.mMode == LOCAL; if (!gContext.mbUsing) { type = GetRotateType(); if (type == ROTATE_SCREEN) { applyRotationLocaly = true; } if (CanActivate() && type != NONE) { ImGui::CaptureMouseFromApp(); gContext.mbUsing = true; 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 - ROTATE_X]); } else { gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - 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) { ImGui::CaptureMouseFromApp(); 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); gContext.mRotationAngleOrigin = gContext.mRotationAngle; matrix_t scaleOrigin; scaleOrigin.Scale(gContext.mModelScaleOrigin); if (applyRotationLocaly) { *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModel; } 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; type = gContext.mCurrentOperation; } } 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 Manipulate(const float *view, const float *projection, OPERATION operation, MODE mode, float *matrix, float *deltaMatrix, float *snap, float *localBounds, float *boundsSnap) { ComputeContext(view, projection, matrix, 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 (camSpacePosition.z < 0.001f) return; // -- int type = NONE; if (gContext.mbEnable) { if (!gContext.mbUsingBounds) { switch (operation) { case ROTATE: HandleRotation(matrix, deltaMatrix, type, snap); break; case TRANSLATE: HandleTranslation(matrix, deltaMatrix, type, snap); break; case SCALE: HandleScale(matrix, deltaMatrix, type, snap); break; } } } if (localBounds && !gContext.mbUsing) HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap); if (!gContext.mbUsingBounds) { switch (operation) { case ROTATE: DrawRotationGizmo(type); break; case TRANSLATE: DrawTranslationGizmo(type); break; case SCALE: DrawScaleGizmo(type); break; } } } void DrawCube(const float *view, const float *projection, float *matrix) { matrix_t viewInverse; viewInverse.Inverse(*(matrix_t*)view); const matrix_t& model = *(matrix_t*)matrix; 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, gContext.mMVP); if (camSpacePosition.z < 0.001f) { skipFace = true; break; } } if (skipFace) continue; // 3D->2D ImVec2 faceCoordsScreen[4]; for (unsigned int iCoord = 0; iCoord < 4; iCoord++) faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); // back face culling vec_t cullPos, cullNormal; cullPos.TransformPoint(faceCoords[0] * 0.5f * invert, model); cullNormal.TransformVector(directionUnary[normalIndex] * invert, model); float dt = Dot(Normalized(cullPos - viewInverse.v.position), Normalized(cullNormal)); if (dt>0.f) continue; // draw face with lighter color gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, directionColor[normalIndex] | 0x808080); } } };