From 079442e7ba796ab3262fa91eb187f77b55d790e3 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 15 May 2026 11:44:01 -0700 Subject: [PATCH] Selection visualization is now fixed. --- neo/engine/tools/radiant/CamWnd.cpp | 220 +++++++++++++++++++++------- 1 file changed, 167 insertions(+), 53 deletions(-) diff --git a/neo/engine/tools/radiant/CamWnd.cpp b/neo/engine/tools/radiant/CamWnd.cpp index cd0e29b8..266273ae 100644 --- a/neo/engine/tools/radiant/CamWnd.cpp +++ b/neo/engine/tools/radiant/CamWnd.cpp @@ -48,7 +48,7 @@ static char THIS_FILE[] = __FILE__; extern void DrawPathLines(); extern qertrace_t Test_Ray(const idVec3& origin, const idVec3& dir, int flags); -extern brush_t *Brush_CreateFaceExtrusion(brush_t *sourceBrush, face_t *sourceFace, float distance); +extern brush_t* Brush_CreateFaceExtrusion(brush_t* sourceBrush, face_t* sourceFace, float distance); extern void Select_ShiftTexture(float x, float y); extern void Select_ScaleTexture(float x, float y); extern void Select_RotateTexture(float amt, bool absolute); @@ -1609,11 +1609,12 @@ from the selected face and linked to the same entity as the source brush. struct camFaceExtrudeState_t { bool active; bool moved; - CCamWnd* cam; - brush_t* sourceBrush; - face_t* sourceFace; - idWinding* sourceWinding; + CCamWnd* cam; + brush_t* sourceBrush; + face_t* sourceFace; + idWinding* sourceWinding; idVec3 normal; + idVec3 hitPoint; CPoint startPoint; float distance; }; @@ -1717,6 +1718,7 @@ static bool CamWnd_FaceExtrudeBegin(CCamWnd* cam, const CPoint& point) { s_faceExtrudeState.sourceFace = trace.face; s_faceExtrudeState.sourceWinding = trace.face->face_winding->Copy(); s_faceExtrudeState.normal = normal; + s_faceExtrudeState.hitPoint = cam->Camera().origin + dir * trace.dist; s_faceExtrudeState.startPoint = point; s_faceExtrudeState.distance = 0.0f; @@ -1727,17 +1729,53 @@ static bool CamWnd_FaceExtrudeBegin(CCamWnd* cam, const CPoint& point) { return true; } +static bool CamWnd_FaceExtrudeDistanceFromRay(CCamWnd* cam, const CPoint& point, float& distance) { + idVec3 rayDir; + CamWnd_FaceExtrudeBuildRay(cam, point, rayDir); + + const idVec3 rayOrigin = cam->Camera().origin; + const idVec3 lineOrigin = s_faceExtrudeState.hitPoint; + const idVec3 lineDir = s_faceExtrudeState.normal; + + // Find the closest point between the current mouse ray and the extrusion + // normal through the original hit point. This makes the drag distance match + // the world-space preview instead of treating one screen pixel as one unit. + 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; + } + + distance = t; + return true; +} + static float CamWnd_FaceExtrudeMouseDistance(CCamWnd* cam, const CPoint& point) { + float distance = 0.0f; + if (CamWnd_FaceExtrudeDistanceFromRay(cam, point, distance)) { + return CamWnd_FaceExtrudeSnapDistance(distance); + } + const int dx = point.x - s_faceExtrudeState.startPoint.x; const int dy = point.y - s_faceExtrudeState.startPoint.y; camera_t& camera = cam->Camera(); idVec3 screenMove = camera.vright * (float)dx - camera.vup * (float)dy; - float distance = DotProduct(screenMove, s_faceExtrudeState.normal); + distance = DotProduct(screenMove, s_faceExtrudeState.normal); - // When the selected face is nearly screen-facing, projection onto the normal - // is too small to be useful. In that case use vertical mouse motion, with - // the sign chosen so dragging up extrudes toward the camera-facing normal. + // When the normal projects poorly onto the screen, fall back to vertical + // mouse motion, with the sign chosen so dragging up extrudes toward the + // camera-facing normal. float verticalDistance = (float)-dy; if (DotProduct(camera.vpn, s_faceExtrudeState.normal) > 0.0f) { verticalDistance = -verticalDistance; @@ -1795,79 +1833,104 @@ static void CamWnd_FaceExtrudeEnd(CCamWnd* cam, bool commit) { Sys_UpdateWindows(W_ALL); } +static void CamWnd_DrawFaceWindingFilled(face_t* face) { + if (face == NULL || face->face_winding == NULL || face->face_winding->GetNumPoints() < 3) { + return; + } + + glBegin(GL_QUADS); + for (int i = 0; i < face->face_winding->GetNumPoints(); i++) { + glVertex3fv((*face->face_winding)[i].ToFloatPtr()); + } + glEnd(); +} + +static void CamWnd_DrawFaceWindingOutline(face_t* face) { + if (face == NULL || face->face_winding == NULL || face->face_winding->GetNumPoints() < 3) { + return; + } + + glBegin(GL_LINE_LOOP); + for (int i = 0; i < face->face_winding->GetNumPoints(); i++) { + glVertex3fv((*face->face_winding)[i].ToFloatPtr()); + } + glEnd(); +} + +static void CamWnd_DrawTransparentFace(face_t* face, float r, float g, float b, float a) { + if (face == NULL || face->face_winding == NULL) { + return; + } + + glPushAttrib(GL_CURRENT_BIT); + globalImages->BindNull(); + glDisable(GL_LIGHTING); + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glColor4f(r, g, b, a); + CamWnd_DrawFaceWindingFilled(face); + glPopAttrib(); +} + static void CamWnd_FaceExtrudeDrawPreview(CCamWnd* cam) { if (!CamWnd_FaceExtrudeIsActive(cam) || s_faceExtrudeState.sourceWinding == NULL) { return; } + + // Always tint the source side while it is selected for extrusion, even before + // the mouse has moved far enough to create a non-zero snapped preview. + CamWnd_DrawTransparentFace(s_faceExtrudeState.sourceFace, 1.0f, 0.0f, 0.0f, 0.25f); + if (idMath::Fabs(s_faceExtrudeState.distance) < CAMWND_FACE_EXTRUDE_EPSILON) { return; } - idWinding* w = s_faceExtrudeState.sourceWinding; - const int numPoints = w->GetNumPoints(); - if (numPoints < 3) { + brush_t* previewBrush = Brush_CreateFaceExtrusion( + s_faceExtrudeState.sourceBrush, + s_faceExtrudeState.sourceFace, + s_faceExtrudeState.distance); + if (previewBrush == NULL) { return; } - idVec3 offset = s_faceExtrudeState.normal * s_faceExtrudeState.distance; + Brush_Build(previewBrush, true, false, false, false); + Brush_RemoveEmptyFaces(previewBrush); + if (previewBrush->brush_faces == NULL) { + Brush_Free(previewBrush); + return; + } glPushAttrib(GL_CURRENT_BIT); globalImages->BindNull(); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); + glDisable(GL_CULL_FACE); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glLineWidth(2.0f); glColor3f(0.1f, 0.9f, 1.0f); - glBegin(GL_LINES); - for (int i = 0; i < numPoints; i++) { - const int j = (i + 1) % numPoints; - idVec3 a = (*w)[i].ToVec3(); - idVec3 b = (*w)[j].ToVec3(); - idVec3 a2 = a + offset; - idVec3 b2 = b + offset; - - glVertex3fv(a.ToFloatPtr()); - glVertex3fv(b.ToFloatPtr()); - glVertex3fv(a2.ToFloatPtr()); - glVertex3fv(b2.ToFloatPtr()); - glVertex3fv(a.ToFloatPtr()); - glVertex3fv(a2.ToFloatPtr()); + for (face_t* face = previewBrush->brush_faces; face; face = face->next) { + CamWnd_DrawFaceWindingOutline(face); } - glEnd(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glColor4f(0.1f, 0.9f, 1.0f, 0.18f); - glBegin(GL_QUADS); - for (int i = 0; i < numPoints; i++) { - idVec3 p = (*w)[i].ToVec3() + offset; - glVertex3fv(p.ToFloatPtr()); + for (face_t* face = previewBrush->brush_faces; face; face = face->next) { + CamWnd_DrawFaceWindingFilled(face); } - glEnd(); - - glBegin(GL_QUADS); - for (int i = 0; i < numPoints; i++) { - const int j = (i + 1) % numPoints; - idVec3 a = (*w)[i].ToVec3(); - idVec3 b = (*w)[j].ToVec3(); - idVec3 a2 = a + offset; - idVec3 b2 = b + offset; - - glVertex3fv(a.ToFloatPtr()); - glVertex3fv(b.ToFloatPtr()); - glVertex3fv(b2.ToFloatPtr()); - glVertex3fv(a2.ToFloatPtr()); - } - glEnd(); glPopAttrib(); + Brush_Free(previewBrush); } + static COLORREF CamWnd_LerpColor(COLORREF a, COLORREF b, float t) { const int ar = GetRValue(a); const int ag = GetGValue(a); @@ -2896,6 +2959,56 @@ void setGLMode(int mode) { } } +static bool CamWnd_FaceHasAllPlanePointsSelected(face_t* face) { + if (face == NULL || face->face_winding == NULL) { + return false; + } + + for (int i = 0; i < 3; i++) { + if (PointInMoveList(&face->planepts[i]) < 0) { + return false; + } + } + return true; +} + +static void CamWnd_DrawMoveSelectedBrushSidesForList(CCamWnd* cam, brush_t* list) { + if (list == NULL || list->next == NULL) { + return; + } + + for (brush_t* brush = list->next; brush != list; brush = brush->next) { + if (CamWnd_MenuFilterBrush(cam, brush)) { + continue; + } + if (brush->pPatch || brush->modelHandle > 0 || brush->entityModel) { + continue; + } + + for (face_t* face = brush->brush_faces; face; face = face->next) { + if (CamWnd_FaceHasAllPlanePointsSelected(face)) { + CamWnd_DrawFaceWindingFilled(face); + } + } + } +} + +static void CamWnd_DrawMoveSelectedBrushSides(CCamWnd* cam) { + glPushAttrib(GL_CURRENT_BIT); + globalImages->BindNull(); + glDisable(GL_LIGHTING); + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glColor4f(1.0f, 0.0f, 0.0f, 0.25f); + + CamWnd_DrawMoveSelectedBrushSidesForList(cam, &active_brushes); + CamWnd_DrawMoveSelectedBrushSidesForList(cam, &selected_brushes); + + glPopAttrib(); +} + extern void glLabeledPoint(idVec4& color, idVec3& point, float size, const char* label); void DrawAxial(face_t* selFace) { @@ -3115,19 +3228,19 @@ void CCamWnd::Cam_Draw() { } // non-zbuffered outline - glDisable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + //glDisable(GL_BLEND); + //glDisable(GL_DEPTH_TEST); + //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); if (renderMode) { - glColor3f(1, 0, 0); + glColor4f(1.0f, 0.0f, 0.0f, 0.25f); for (int i = 0; i < nCount; i++) { face_t* selFace = reinterpret_cast (g_ptrSelectedFaces.GetAt(i)); Face_Draw(selFace); } } - glColor3f(1, 1, 1); + glColor4f(1.0f, 0.0f, 0.0f, 0.25f); for (brush = pList->next; brush != pList; brush = brush->next) { if (CamWnd_MenuFilterBrush(this, brush)) { continue; @@ -3141,6 +3254,7 @@ void CCamWnd::Cam_Draw() { } } + CamWnd_DrawMoveSelectedBrushSides(this); CamWnd_FaceExtrudeDrawPreview(this); // edge / vertex flags