From 01b8ad9f7e31b2eaa3310978e2dffcde748b841c Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Sun, 17 May 2026 23:14:29 -0700 Subject: [PATCH] Added axis movement. --- neo/engine/tools/radiant/CamWnd.cpp | 601 ++++++++++++++++++++++++++++ 1 file changed, 601 insertions(+) diff --git a/neo/engine/tools/radiant/CamWnd.cpp b/neo/engine/tools/radiant/CamWnd.cpp index 266273ae..aeadaa5b 100644 --- a/neo/engine/tools/radiant/CamWnd.cpp +++ b/neo/engine/tools/radiant/CamWnd.cpp @@ -52,6 +52,12 @@ extern brush_t* Brush_CreateFaceExtrusion(brush_t* sourceBrush, face_t* sourceFa extern void Select_ShiftTexture(float x, float y); extern void Select_ScaleTexture(float x, float y); extern void Select_RotateTexture(float amt, bool absolute); +extern void Select_Move(idVec3 delta, bool bSnap); +extern void Select_Ray(idVec3 origin, idVec3 dir, int flags); +extern void Select_Deselect(bool bDeselectFaces); +extern void Select_RotateAxis(int axis, float deg, bool bPaint, bool bMouse); +extern void Select_InitializeRotation(); +extern void Select_FinalizeRotation(); /* ======================== @@ -1931,6 +1937,582 @@ static void CamWnd_FaceExtrudeDrawPreview(CCamWnd* cam) { + + +/* +======================== +Camera selection axis gizmo + +A selected object now gets a small world-axis handle in the camera view. +Hover an axis and left-drag to translate along that axis. Holding Shift while +starting the drag converts the same handle into yaw-only rotation around the Z +axis; no pitch/roll rotation handles are exposed here. +======================== +*/ +#define CAMWND_GIZMO_AXIS_NONE -1 +#define CAMWND_GIZMO_AXIS_X 0 +#define CAMWND_GIZMO_AXIS_Y 1 +#define CAMWND_GIZMO_AXIS_Z 2 +#define CAMWND_GIZMO_HIT_PIXELS 9.0f +#define CAMWND_GIZMO_DRAW_PIXELS 96.0f +#define CAMWND_GIZMO_ROTATE_AXIS 2 + +struct camWndGizmoState_t { + bool active; + bool rotating; + int hoverAxis; + int activeAxis; + CCamWnd* cam; + CPoint startPoint; + CPoint lastPoint; + idVec3 startCenter; + idVec3 axisDir; + float axisLength; + float startScalar; + float lastScalar; + float rotationAmount; +}; + +static camWndGizmoState_t s_camWndGizmoState; + +static bool CamWnd_GizmoSelectionBounds(idVec3& mins, idVec3& maxs) { + if (selected_brushes.next == NULL || selected_brushes.next == &selected_brushes) { + return false; + } + + mins[0] = mins[1] = mins[2] = 999999.0f; + maxs[0] = maxs[1] = maxs[2] = -999999.0f; + bool haveBounds = false; + + for (brush_t* brush = selected_brushes.next; brush != &selected_brushes; brush = brush->next) { + if (brush == NULL) { + continue; + } + for (int i = 0; i < 3; i++) { + if (brush->mins[i] < mins[i]) { + mins[i] = brush->mins[i]; + } + if (brush->maxs[i] > maxs[i]) { + maxs[i] = brush->maxs[i]; + } + } + haveBounds = true; + } + + return haveBounds; +} + +static bool CamWnd_GizmoSelectionCenter(idVec3& center, idVec3* outMins = NULL, idVec3* outMaxs = NULL) { + idVec3 mins; + idVec3 maxs; + if (!CamWnd_GizmoSelectionBounds(mins, maxs)) { + return false; + } + + center[0] = (mins[0] + maxs[0]) * 0.5f; + center[1] = (mins[1] + maxs[1]) * 0.5f; + center[2] = (mins[2] + maxs[2]) * 0.5f; + + center += g_qeglobals.d_select_translate; + if (outMins) { + *outMins = mins; + } + if (outMaxs) { + *outMaxs = maxs; + } + return true; +} + +static idVec3 CamWnd_GizmoAxisVector(int axis) { + idVec3 v; + v[0] = v[1] = v[2] = 0.0f; + if (axis >= 0 && axis < 3) { + v[axis] = 1.0f; + } + return v; +} + +static float CamWnd_GizmoGridSize() { + float gridSize = (float)g_qeglobals.d_gridsize; + if (gridSize <= 0.0f) { + gridSize = 1.0f; + } + return gridSize; +} + +static float CamWnd_GizmoSnapScalar(float scalar) { + if (g_PrefsDlg.m_bNoClamp) { + return scalar; + } + const float gridSize = CamWnd_GizmoGridSize(); + return floor(scalar / gridSize + 0.5f) * gridSize; +} + +static float CamWnd_GizmoWorldPerPixel(CCamWnd* cam, const idVec3& center) { + if (cam == NULL) { + return 1.0f; + } + + camera_t& camera = cam->Camera(); + idVec3 toCenter = center - camera.origin; + float depth = DotProduct(toCenter, camera.vpn); + if (depth < 8.0f) { + depth = toCenter.Length(); + if (depth < 8.0f) { + depth = 8.0f; + } + } + + float halfWidth = (float)camera.width * 0.5f; + if (halfWidth < 1.0f) { + halfWidth = 1.0f; + } + return depth / halfWidth; +} + +static float CamWnd_GizmoAxisLength(CCamWnd* cam, const idVec3& center, const idVec3& mins, const idVec3& maxs) { + idVec3 size = maxs - mins; + float selectionSize = size.Length() * 0.55f; + float cameraSize = CamWnd_GizmoWorldPerPixel(cam, center) * CAMWND_GIZMO_DRAW_PIXELS; + float length = cameraSize; + + if (length < selectionSize) { + length = selectionSize; + } + if (length < 32.0f) { + length = 32.0f; + } + return length; +} + +static bool CamWnd_GizmoProjectPoint(CCamWnd* cam, const idVec3& point, CPoint& out) { + if (cam == NULL) { + return false; + } + + camera_t& camera = cam->Camera(); + idVec3 local = point - camera.origin; + float depth = DotProduct(local, camera.vpn); + if (depth <= 1.0f) { + return false; + } + + float halfWidth = (float)camera.width * 0.5f; + if (halfWidth <= 0.0f) { + return false; + } + + float r = DotProduct(local, camera.vright) / depth; + float u = DotProduct(local, camera.vup) / depth; + float sx = (float)camera.width * 0.5f + r * halfWidth; + float syBottom = (float)camera.height * 0.5f + u * halfWidth; + + CRect rect; + cam->GetClientRect(rect); + out.x = (int)(sx + 0.5f); + out.y = rect.bottom - 1 - (int)(syBottom + 0.5f); + return true; +} + +static float CamWnd_GizmoDistanceToSegment(const CPoint& point, const CPoint& a, const CPoint& b, float& tOut) { + float px = (float)point.x; + float py = (float)point.y; + float ax = (float)a.x; + float ay = (float)a.y; + float bx = (float)b.x; + float by = (float)b.y; + float dx = bx - ax; + float dy = by - ay; + float lenSqr = dx * dx + dy * dy; + + if (lenSqr <= 0.0001f) { + tOut = 0.0f; + float ex = px - ax; + float ey = py - ay; + return sqrt(ex * ex + ey * ey); + } + + float t = ((px - ax) * dx + (py - ay) * dy) / lenSqr; + if (t < 0.0f) { + t = 0.0f; + } + else if (t > 1.0f) { + t = 1.0f; + } + tOut = t; + + float cx = ax + dx * t; + float cy = ay + dy * t; + float ex = px - cx; + float ey = py - cy; + return sqrt(ex * ex + ey * ey); +} + +static int CamWnd_GizmoHitTest(CCamWnd* cam, const CPoint& point, float* bestDistance = NULL) { + idVec3 center; + idVec3 mins; + idVec3 maxs; + if (!CamWnd_GizmoSelectionCenter(center, &mins, &maxs)) { + if (bestDistance) { + *bestDistance = -1.0f; + } + return CAMWND_GIZMO_AXIS_NONE; + } + + const float axisLength = CamWnd_GizmoAxisLength(cam, center, mins, maxs); + float best = CAMWND_GIZMO_HIT_PIXELS; + int bestAxis = CAMWND_GIZMO_AXIS_NONE; + + CPoint start; + if (!CamWnd_GizmoProjectPoint(cam, center, start)) { + if (bestDistance) { + *bestDistance = -1.0f; + } + return CAMWND_GIZMO_AXIS_NONE; + } + + for (int axis = 0; axis < 3; axis++) { + idVec3 axisDir = CamWnd_GizmoAxisVector(axis); + CPoint end; + if (!CamWnd_GizmoProjectPoint(cam, center + axisDir * axisLength, end)) { + continue; + } + + float segmentT = 0.0f; + float dist = CamWnd_GizmoDistanceToSegment(point, start, end, segmentT); + if (dist <= best && segmentT >= 0.08f) { + best = dist; + bestAxis = axis; + } + } + + if (bestDistance) { + *bestDistance = (bestAxis == CAMWND_GIZMO_AXIS_NONE) ? -1.0f : best; + } + return bestAxis; +} + +static bool CamWnd_GizmoAxisScalarFromRay(CCamWnd* cam, const CPoint& point, const idVec3& lineOrigin, const idVec3& lineDir, float& scalar) { + idVec3 rayDir; + CamWnd_FaceExtrudeBuildRay(cam, point, rayDir); + + const idVec3 rayOrigin = cam->Camera().origin; + const float b = DotProduct(rayDir, lineDir); + const float denom = 1.0f - b * b; + if (denom < 0.0001f) { + return false; + } + + idVec3 w = rayOrigin - lineOrigin; + const float d = DotProduct(rayDir, w); + const float e = DotProduct(lineDir, w); + const float t = (e - b * d) / denom; + const float rayT = b * t - d; + if (rayT < 0.0f) { + return false; + } + + scalar = t; + return true; +} + +static bool CamWnd_GizmoScreenDeltaScalar(CCamWnd* cam, const CPoint& point, float& scalar) { + CPoint start; + CPoint end; + if (!CamWnd_GizmoProjectPoint(cam, s_camWndGizmoState.startCenter, start) || + !CamWnd_GizmoProjectPoint(cam, s_camWndGizmoState.startCenter + s_camWndGizmoState.axisDir * s_camWndGizmoState.axisLength, end)) { + return false; + } + + float dx = (float)(end.x - start.x); + float dy = (float)(end.y - start.y); + float len = sqrt(dx * dx + dy * dy); + if (len < 0.001f) { + return false; + } + + dx /= len; + dy /= len; + float mouseDx = (float)(point.x - s_camWndGizmoState.startPoint.x); + float mouseDy = (float)(point.y - s_camWndGizmoState.startPoint.y); + scalar = (mouseDx * dx + mouseDy * dy) * (s_camWndGizmoState.axisLength / len); + return true; +} + +static bool CamWnd_GizmoBegin(CCamWnd* cam, const CPoint& point, UINT nFlags) { + if (cam == NULL || !cam->GetSafeHwnd() || (nFlags & MK_CONTROL)) { + return false; + } + + const int axis = CamWnd_GizmoHitTest(cam, point); + if (axis == CAMWND_GIZMO_AXIS_NONE) { + return false; + } + + idVec3 center; + idVec3 mins; + idVec3 maxs; + if (!CamWnd_GizmoSelectionCenter(center, &mins, &maxs)) { + return false; + } + + memset(&s_camWndGizmoState, 0, sizeof(s_camWndGizmoState)); + s_camWndGizmoState.active = true; + s_camWndGizmoState.rotating = ((nFlags & MK_SHIFT) != 0) || ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0); + s_camWndGizmoState.hoverAxis = axis; + s_camWndGizmoState.activeAxis = s_camWndGizmoState.rotating ? CAMWND_GIZMO_AXIS_Z : axis; + s_camWndGizmoState.cam = cam; + s_camWndGizmoState.startPoint = point; + s_camWndGizmoState.lastPoint = point; + s_camWndGizmoState.startCenter = center; + s_camWndGizmoState.axisDir = CamWnd_GizmoAxisVector(s_camWndGizmoState.rotating ? CAMWND_GIZMO_AXIS_Z : axis); + s_camWndGizmoState.axisLength = CamWnd_GizmoAxisLength(cam, center, mins, maxs); + s_camWndGizmoState.startScalar = 0.0f; + s_camWndGizmoState.lastScalar = 0.0f; + s_camWndGizmoState.rotationAmount = 0.0f; + + CamWnd_GizmoAxisScalarFromRay(cam, point, center, s_camWndGizmoState.axisDir, s_camWndGizmoState.startScalar); + + if (s_camWndGizmoState.rotating) { + Select_InitializeRotation(); + } + + cam->SetFocus(); + cam->SetCapture(); + SetCursor(LoadCursor(NULL, s_camWndGizmoState.rotating ? IDC_CROSS : IDC_SIZEALL)); + Sys_Status(s_camWndGizmoState.rotating ? "Shift-drag gizmo: yaw rotate selected object.\n" : "Drag gizmo axis to move selected object.\n"); + Sys_UpdateWindows(W_CAMERA | W_XY | W_Z); + return true; +} + +static bool CamWnd_GizmoIsActive(CCamWnd* cam) { + return s_camWndGizmoState.active && s_camWndGizmoState.cam == cam; +} + +static void CamWnd_GizmoEnd(CCamWnd* cam, bool commit) { + if (!CamWnd_GizmoIsActive(cam)) { + return; + } + + if (::GetCapture() == cam->GetSafeHwnd()) { + ::ReleaseCapture(); + } + + if (s_camWndGizmoState.rotating) { + Select_FinalizeRotation(); + } + + + if (!commit) { + Sys_Status("camera gizmo drag ended.\n"); + } + g_pParentWnd->SetStatusText(3, ""); + const int lastHoverAxis = s_camWndGizmoState.hoverAxis; + memset(&s_camWndGizmoState, 0, sizeof(s_camWndGizmoState)); + s_camWndGizmoState.hoverAxis = lastHoverAxis; + Sys_UpdateWindows(W_ALL); +} + +static bool CamWnd_GizmoMouseMove(CCamWnd* cam, const CPoint& point, UINT nFlags) { + if (CamWnd_GizmoIsActive(cam)) { + if (s_camWndGizmoState.rotating) { + const int dx = point.x - s_camWndGizmoState.lastPoint.x; + const int dy = point.y - s_camWndGizmoState.lastPoint.y; + float deltaAngle = (float)dx * 0.5f; + if (idMath::Fabs(deltaAngle) < idMath::Fabs((float)dy) * 0.15f) { + deltaAngle = (float)-dy * 0.5f; + } + if (deltaAngle != 0.0f) { + Select_RotateAxis(CAMWND_GIZMO_ROTATE_AXIS, deltaAngle, false, false); + cam->MarkWorldDirty(); + s_camWndGizmoState.rotationAmount += deltaAngle; + CString strStatus; + strStatus.Format("Yaw rotation: %.1f", s_camWndGizmoState.rotationAmount); + g_pParentWnd->SetStatusText(3, strStatus); + Sys_UpdateWindows(W_CAMERA | W_XY | W_Z); + } + } + else { + float currentScalar = 0.0f; + float moveScalar = 0.0f; + if (CamWnd_GizmoAxisScalarFromRay(cam, point, s_camWndGizmoState.startCenter, s_camWndGizmoState.axisDir, currentScalar)) { + moveScalar = currentScalar - s_camWndGizmoState.startScalar; + } + else if (!CamWnd_GizmoScreenDeltaScalar(cam, point, moveScalar)) { + moveScalar = 0.0f; + } + + moveScalar = CamWnd_GizmoSnapScalar(moveScalar); + float deltaScalar = moveScalar - s_camWndGizmoState.lastScalar; + if (deltaScalar != 0.0f) { + idVec3 delta = s_camWndGizmoState.axisDir * deltaScalar; + Select_Move(delta, true); + cam->MarkWorldDirty(); + s_camWndGizmoState.lastScalar = moveScalar; + CString strStatus; + strStatus.Format("Axis move: %.1f", moveScalar); + g_pParentWnd->SetStatusText(3, strStatus); + Sys_UpdateWindows(W_CAMERA | W_XY | W_Z); + } + } + + s_camWndGizmoState.lastPoint = point; + SetCursor(LoadCursor(NULL, s_camWndGizmoState.rotating ? IDC_CROSS : IDC_SIZEALL)); + return true; + } + + if ((nFlags & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) == 0) { + int oldHover = s_camWndGizmoState.hoverAxis; + s_camWndGizmoState.hoverAxis = CamWnd_GizmoHitTest(cam, point); + if (s_camWndGizmoState.hoverAxis != CAMWND_GIZMO_AXIS_NONE) { + SetCursor(LoadCursor(NULL, (GetAsyncKeyState(VK_SHIFT) & 0x8000) ? IDC_CROSS : IDC_SIZEALL)); + } + if (oldHover != s_camWndGizmoState.hoverAxis) { + Sys_UpdateWindows(W_CAMERA); + } + } + + return false; +} + +static bool CamWnd_GizmoSelectObjectClick(CCamWnd* cam, const CPoint& point, UINT nFlags) { + if (cam == NULL || !cam->GetSafeHwnd()) { + return false; + } + if (nFlags & (MK_CONTROL | MK_SHIFT)) { + return false; + } + if (GetAsyncKeyState(VK_MENU) & 0x8000) { + return false; + } + + idVec3 dir; + CamWnd_FaceExtrudeBuildRay(cam, point, dir); + qertrace_t trace = Test_Ray(cam->Camera().origin, dir, 0); + if (trace.brush == NULL || trace.dist <= 0.0f || trace.dist >= HUGE_DISTANCE) { + return false; + } + if (CamWnd_MenuFilterBrush(cam, trace.brush) || Map_IsBrushFiltered(trace.brush)) { + return false; + } + if (trace.brush->owner != NULL && !Outliner_IsEntityVisible(trace.brush->owner)) { + return false; + } + + // If the click hit something outside the current selection, make it the new + // selection and let the axis draw immediately. Clicks on an already-selected + // object still fall through to the original drag code for compatibility. + if (!trace.selected || selected_brushes.next == &selected_brushes) { + Select_Deselect(true); + Select_Ray(cam->Camera().origin, dir, 0); + s_camWndGizmoState.hoverAxis = CAMWND_GIZMO_AXIS_NONE; + Sys_UpdateWindows(W_ALL); + return true; + } + + return false; +} + +static void CamWnd_GizmoColorForAxis(int axis, bool active) { + if (active) { + glColor3f(1.0f, 1.0f, 0.0f); + return; + } + + switch (axis) { + case CAMWND_GIZMO_AXIS_X: + glColor3f(1.0f, 0.1f, 0.1f); + break; + case CAMWND_GIZMO_AXIS_Y: + glColor3f(0.1f, 1.0f, 0.1f); + break; + case CAMWND_GIZMO_AXIS_Z: + glColor3f(0.25f, 0.45f, 1.0f); + break; + default: + glColor3f(1.0f, 1.0f, 1.0f); + break; + } +} + +static idVec3 CamWnd_GizmoCross(const idVec3& a, const idVec3& b) { + idVec3 out; + out[0] = a[1] * b[2] - a[2] * b[1]; + out[1] = a[2] * b[0] - a[0] * b[2]; + out[2] = a[0] * b[1] - a[1] * b[0]; + return out; +} + +static void CamWnd_GizmoDrawAxisTip(const idVec3& axisEnd, const idVec3& axisDir, float tipSize) { + idVec3 side1 = CamWnd_GizmoCross(axisDir, idVec3(0.0f, 0.0f, 1.0f)); + if (side1.LengthSqr() < 0.001f) { + side1 = CamWnd_GizmoCross(axisDir, idVec3(0.0f, 1.0f, 0.0f)); + } + side1.Normalize(); + idVec3 side2 = CamWnd_GizmoCross(axisDir, side1); + side2.Normalize(); + + const idVec3 base = axisEnd - axisDir * tipSize; + glBegin(GL_LINES); + glVertex3fv(axisEnd.ToFloatPtr()); + glVertex3fv((base + side1 * (tipSize * 0.45f)).ToFloatPtr()); + glVertex3fv(axisEnd.ToFloatPtr()); + glVertex3fv((base - side1 * (tipSize * 0.45f)).ToFloatPtr()); + glVertex3fv(axisEnd.ToFloatPtr()); + glVertex3fv((base + side2 * (tipSize * 0.45f)).ToFloatPtr()); + glVertex3fv(axisEnd.ToFloatPtr()); + glVertex3fv((base - side2 * (tipSize * 0.45f)).ToFloatPtr()); + glEnd(); +} + +static void CamWnd_GizmoDraw(CCamWnd* cam) { + idVec3 center; + idVec3 mins; + idVec3 maxs; + if (!CamWnd_GizmoSelectionCenter(center, &mins, &maxs)) { + return; + } + + float axisLength = CamWnd_GizmoAxisLength(cam, center, mins, maxs); + float tipSize = axisLength * 0.12f; + if (tipSize < 8.0f) { + tipSize = 8.0f; + } + else if (tipSize > 32.0f) { + tipSize = 32.0f; + } + + glPushAttrib(GL_CURRENT_BIT); + globalImages->BindNull(); + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glLineWidth(3.0f); + + for (int axis = 0; axis < 3; axis++) { + idVec3 axisDir = CamWnd_GizmoAxisVector(axis); + idVec3 axisEnd = center + axisDir * axisLength; + bool active = (CamWnd_GizmoIsActive(cam) && s_camWndGizmoState.activeAxis == axis) || + (!s_camWndGizmoState.active && s_camWndGizmoState.hoverAxis == axis); + + CamWnd_GizmoColorForAxis(axis, active); + glBegin(GL_LINES); + glVertex3fv(center.ToFloatPtr()); + glVertex3fv(axisEnd.ToFloatPtr()); + glEnd(); + CamWnd_GizmoDrawAxisTip(axisEnd, axisDir, tipSize); + } + + glPointSize(6.0f); + glColor3f(1.0f, 1.0f, 1.0f); + glBegin(GL_POINTS); + glVertex3fv(center.ToFloatPtr()); + glEnd(); + + glPopAttrib(); +} + static COLORREF CamWnd_LerpColor(COLORREF a, COLORREF b, float t) { const int ar = GetRValue(a); const int ag = GetGValue(a); @@ -2154,6 +2736,7 @@ INT_PTR WINAPI CamWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CCamWnd* cam = DYNAMIC_DOWNCAST(CCamWnd, wnd); if (cam) { CamWnd_FaceExtrudeEnd(cam, false); + CamWnd_GizmoEnd(cam, false); CameraNav_Stop(cam); } SendMessage(hWnd, WM_NCACTIVATE, FALSE, 0); @@ -2274,6 +2857,7 @@ void CCamWnd::SetXYFriend(CXYWnd* pWnd) { */ void CCamWnd::OnDestroy() { CamWnd_FaceExtrudeEnd(this, false); + CamWnd_GizmoEnd(this, false); CamWnd_DestroyMenuBar(this); CWnd::OnDestroy(); } @@ -2306,6 +2890,11 @@ void CCamWnd::OnMouseMove(UINT nFlags, CPoint point) { return; } + if (CamWnd_GizmoMouseMove(this, point, nFlags)) { + m_ptLastCursor = point; + return; + } + if (GetCapture() == this && (GetAsyncKeyState(VK_MENU) & 0x8000)) { // Alt-drag texture manipulation. The old condition excluded shift/control // before checking them, so alt+shift and alt+control never reached the @@ -2348,6 +2937,12 @@ void CCamWnd::OnLButtonDown(UINT nFlags, CPoint point) { if ((nFlags & MK_CONTROL) && (nFlags & MK_SHIFT) && CamWnd_FaceExtrudeBegin(this, point)) { return; } + if (CamWnd_GizmoBegin(this, point, nFlags)) { + return; + } + if (CamWnd_GizmoSelectObjectClick(this, point, nFlags)) { + return; + } OriginalMouseDown(nFlags, point); } @@ -2356,6 +2951,11 @@ void CCamWnd::OnLButtonDown(UINT nFlags, CPoint point) { ======================================================================================================================= */ void CCamWnd::OnLButtonUp(UINT nFlags, CPoint point) { + if (CamWnd_GizmoIsActive(this)) { + CamWnd_GizmoMouseMove(this, point, nFlags); + CamWnd_GizmoEnd(this, true); + return; + } if (CamWnd_FaceExtrudeIsActive(this)) { CamWnd_FaceExtrudeMouseMove(this, point); CamWnd_FaceExtrudeEnd(this, true); @@ -3256,6 +3856,7 @@ void CCamWnd::Cam_Draw() { CamWnd_DrawMoveSelectedBrushSides(this); CamWnd_FaceExtrudeDrawPreview(this); + CamWnd_GizmoDraw(this); // edge / vertex flags if (g_qeglobals.d_select_mode == sel_vertex) {