Added trenchbroom style brush extrusion.

This commit is contained in:
Justin Marshall
2026-05-14 21:03:44 -07:00
parent 2526bb2513
commit ea14ecb595
3 changed files with 1243 additions and 771 deletions
+296
View File
@@ -48,6 +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 void Select_ShiftTexture(float x, float y);
extern void Select_ScaleTexture(float x, float y);
extern void Select_RotateTexture(float amt, bool absolute);
@@ -1584,6 +1585,279 @@ static bool CamWnd_MenuFilterBrush(CCamWnd* cam, brush_t* brush) {
}
/*
========================
Ctrl+Shift camera face extrusion
Ctrl+Shift+LMB on a brush face starts a camera-view extrusion preview. The
preview is only drawn while dragging. On mouse release a real brush is created
from the selected face and linked to the same entity as the source brush.
========================
*/
#define CAMWND_FACE_EXTRUDE_EPSILON 0.01f
struct camFaceExtrudeState_t {
bool active;
bool moved;
CCamWnd* cam;
brush_t* sourceBrush;
face_t* sourceFace;
idWinding* sourceWinding;
idVec3 normal;
CPoint startPoint;
float distance;
};
static camFaceExtrudeState_t s_faceExtrudeState;
static bool CamWnd_FaceExtrudeIsActive(CCamWnd* cam) {
return s_faceExtrudeState.active && s_faceExtrudeState.cam == cam;
}
static void CamWnd_FaceExtrudeReset() {
if (s_faceExtrudeState.sourceWinding) {
delete s_faceExtrudeState.sourceWinding;
}
memset(&s_faceExtrudeState, 0, sizeof(s_faceExtrudeState));
}
static float CamWnd_FaceExtrudeGridSize() {
float gridSize = (float)g_qeglobals.d_gridsize;
if (gridSize <= 0.0f) {
gridSize = 1.0f;
}
return gridSize;
}
static float CamWnd_FaceExtrudeSnapDistance(float distance) {
const float gridSize = CamWnd_FaceExtrudeGridSize();
if (idMath::Fabs(distance) < gridSize * 0.5f) {
return 0.0f;
}
return floor(distance / gridSize + (distance >= 0.0f ? 0.5f : -0.5f)) * gridSize;
}
static void CamWnd_FaceExtrudeBuildRay(CCamWnd* cam, const CPoint& point, idVec3& dir) {
CRect rect;
cam->GetClientRect(rect);
camera_t& camera = cam->Camera();
const int x = point.x;
const int y = rect.bottom - 1 - point.y;
float u = 0.0f;
float r = 0.0f;
if (camera.width > 0) {
u = (float)(y - camera.height / 2) / (camera.width / 2);
r = (float)(x - camera.width / 2) / (camera.width / 2);
}
for (int i = 0; i < 3; i++) {
dir[i] = camera.vpn[i] + camera.vright[i] * r + camera.vup[i] * u;
}
dir.Normalize();
}
static bool CamWnd_FaceExtrudeBrushAllowed(CCamWnd* cam, brush_t* brush) {
if (brush == NULL || brush->owner == NULL || brush->owner->eclass == NULL) {
return false;
}
if (brush->pPatch || brush->modelHandle > 0 || brush->entityModel) {
return false;
}
if (brush->owner->eclass->fixedsize) {
return false;
}
if (FilterBrush(brush) || CamWnd_MenuFilterBrush(cam, brush) || Map_IsBrushFiltered(brush)) {
return false;
}
return true;
}
static bool CamWnd_FaceExtrudeBegin(CCamWnd* cam, const CPoint& point) {
if (cam == NULL || !cam->GetSafeHwnd()) {
return false;
}
idVec3 dir;
CamWnd_FaceExtrudeBuildRay(cam, point, dir);
qertrace_t trace = Test_Ray(cam->Camera().origin, dir, 0);
if (trace.brush == NULL || trace.face == NULL || trace.face->face_winding == NULL) {
return false;
}
if (trace.face->face_winding->GetNumPoints() < 3) {
return false;
}
if (!CamWnd_FaceExtrudeBrushAllowed(cam, trace.brush)) {
return false;
}
idVec3 normal = trace.face->plane.Normal();
if (normal.LengthSqr() < 0.0001f) {
return false;
}
normal.Normalize();
CamWnd_FaceExtrudeReset();
s_faceExtrudeState.active = true;
s_faceExtrudeState.moved = false;
s_faceExtrudeState.cam = cam;
s_faceExtrudeState.sourceBrush = trace.brush;
s_faceExtrudeState.sourceFace = trace.face;
s_faceExtrudeState.sourceWinding = trace.face->face_winding->Copy();
s_faceExtrudeState.normal = normal;
s_faceExtrudeState.startPoint = point;
s_faceExtrudeState.distance = 0.0f;
cam->SetFocus();
cam->SetCapture();
Sys_Status("Drag to extrude face; release mouse to create new brush.\n");
Sys_UpdateWindows(W_CAMERA);
return true;
}
static float CamWnd_FaceExtrudeMouseDistance(CCamWnd* cam, const CPoint& point) {
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);
// 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.
float verticalDistance = (float)-dy;
if (DotProduct(camera.vpn, s_faceExtrudeState.normal) > 0.0f) {
verticalDistance = -verticalDistance;
}
if (idMath::Fabs(distance) < idMath::Fabs(verticalDistance) * 0.25f) {
distance = verticalDistance;
}
return CamWnd_FaceExtrudeSnapDistance(distance);
}
static bool CamWnd_FaceExtrudeMouseMove(CCamWnd* cam, const CPoint& point) {
if (!CamWnd_FaceExtrudeIsActive(cam)) {
return false;
}
s_faceExtrudeState.distance = CamWnd_FaceExtrudeMouseDistance(cam, point);
s_faceExtrudeState.moved = true;
Sys_UpdateWindows(W_CAMERA | W_XY | W_Z);
return true;
}
static void CamWnd_FaceExtrudeEnd(CCamWnd* cam, bool commit) {
if (!CamWnd_FaceExtrudeIsActive(cam)) {
return;
}
brush_t* sourceBrush = s_faceExtrudeState.sourceBrush;
face_t* sourceFace = s_faceExtrudeState.sourceFace;
float distance = s_faceExtrudeState.distance;
if (::GetCapture() == cam->GetSafeHwnd()) {
::ReleaseCapture();
}
if (commit && sourceBrush && sourceFace && idMath::Fabs(distance) >= CAMWND_FACE_EXTRUDE_EPSILON) {
brush_t* newBrush = Brush_CreateFaceExtrusion(sourceBrush, sourceFace, distance);
if (newBrush != NULL) {
Brush_AddToList(newBrush, &selected_brushes);
Entity_LinkBrush(sourceBrush->owner ? sourceBrush->owner : world_entity, newBrush);
Brush_Build(newBrush, true, true);
Brush_RemoveEmptyFaces(newBrush);
if (newBrush->brush_faces == NULL) {
Brush_Free(newBrush);
Sys_Status("Face extrusion produced an empty brush.\n");
}
else {
Sys_Status("Face extrusion created a new brush.\n");
}
}
}
CamWnd_FaceExtrudeReset();
Sys_UpdateWindows(W_ALL);
}
static void CamWnd_FaceExtrudeDrawPreview(CCamWnd* cam) {
if (!CamWnd_FaceExtrudeIsActive(cam) || s_faceExtrudeState.sourceWinding == NULL) {
return;
}
if (idMath::Fabs(s_faceExtrudeState.distance) < CAMWND_FACE_EXTRUDE_EPSILON) {
return;
}
idWinding* w = s_faceExtrudeState.sourceWinding;
const int numPoints = w->GetNumPoints();
if (numPoints < 3) {
return;
}
idVec3 offset = s_faceExtrudeState.normal * s_faceExtrudeState.distance;
glPushAttrib(GL_CURRENT_BIT);
globalImages->BindNull();
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
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());
}
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());
}
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();
}
static COLORREF CamWnd_LerpColor(COLORREF a, COLORREF b, float t) {
const int ar = GetRValue(a);
const int ag = GetGValue(a);
@@ -1796,6 +2070,7 @@ INT_PTR WINAPI CamWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
CWnd* wnd = CWnd::FromHandlePermanent(hWnd);
CCamWnd* cam = DYNAMIC_DOWNCAST(CCamWnd, wnd);
if (cam) {
CamWnd_FaceExtrudeEnd(cam, false);
CameraNav_Stop(cam);
}
SendMessage(hWnd, WM_NCACTIVATE, FALSE, 0);
@@ -1866,6 +2141,10 @@ BOOL CCamWnd::PreCreateWindow(CREATESTRUCT& cs) {
=======================================================================================================================
*/
void CCamWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
if (CamWnd_FaceExtrudeIsActive(this) && nChar == VK_ESCAPE) {
CamWnd_FaceExtrudeEnd(this, false);
return;
}
if (CameraNav_HandleKeyDown(this, nChar)) {
return;
}
@@ -1911,6 +2190,7 @@ void CCamWnd::SetXYFriend(CXYWnd* pWnd) {
=======================================================================================================================
*/
void CCamWnd::OnDestroy() {
CamWnd_FaceExtrudeEnd(this, false);
CamWnd_DestroyMenuBar(this);
CWnd::OnDestroy();
}
@@ -1933,6 +2213,11 @@ void CCamWnd::OnMouseMove(UINT nFlags, CPoint point) {
CRect r;
GetClientRect(r);
if (CamWnd_FaceExtrudeMouseMove(this, point)) {
m_ptLastCursor = point;
return;
}
if (CameraNav_MouseMove(this)) {
m_ptLastCursor = point;
return;
@@ -1977,6 +2262,9 @@ void CCamWnd::OnLButtonDown(UINT nFlags, CPoint point) {
Sys_UpdateWindows(W_ALL);
return;
}
if ((nFlags & MK_CONTROL) && (nFlags & MK_SHIFT) && CamWnd_FaceExtrudeBegin(this, point)) {
return;
}
OriginalMouseDown(nFlags, point);
}
@@ -1985,6 +2273,11 @@ void CCamWnd::OnLButtonDown(UINT nFlags, CPoint point) {
=======================================================================================================================
*/
void CCamWnd::OnLButtonUp(UINT nFlags, CPoint point) {
if (CamWnd_FaceExtrudeIsActive(this)) {
CamWnd_FaceExtrudeMouseMove(this, point);
CamWnd_FaceExtrudeEnd(this, true);
return;
}
OriginalMouseUp(nFlags, point);
}
@@ -2827,6 +3120,9 @@ void CCamWnd::Cam_Draw() {
Face_Draw(face);
}
}
CamWnd_FaceExtrudeDrawPreview(this);
// edge / vertex flags
if (g_qeglobals.d_select_mode == sel_vertex) {
glPointSize(4);