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);
File diff suppressed because it is too large Load Diff
+48 -47
View File
@@ -2,9 +2,9 @@
===========================================================================
IceTech GPL Source Code
Copyright (C) 2026 Justin Marshall
Copyright (C) 2026 Justin Marshall
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
IceTech Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -28,52 +28,53 @@ If you have questions concerning this license or the applicable additional terms
// brush.h
brush_t * Brush_Alloc();
void Brush_Free (brush_t *b, bool bRemoveNode = true);
int Brush_MemorySize(brush_t *b);
void Brush_MakeSided (int sides);
void Brush_MakeSidedCone (int sides);
void Brush_Move (brush_t *b, const idVec3 move, bool bSnap = true, bool updateOrigin = true);
int Brush_MoveVertex(brush_t *b, const idVec3 &vertex, const idVec3 &delta, idVec3 &end, bool bSnap);
void Brush_ResetFaceOriginals(brush_t *b);
brush_t * Brush_Parse (const idVec3 origin);
face_t * Brush_Ray (idVec3 origin, idVec3 dir, brush_t *b, float *dist, bool testPrimitive = false);
void Brush_RemoveFromList (brush_t *b);
void Brush_AddToList (brush_t *b, brush_t *list);
void Brush_Build(brush_t *b, bool bSnap = true, bool bMarkMap = true, bool bConvert = false, bool updateLights = true);
void Brush_BuildWindings( brush_t *b, bool bSnap = true, bool keepOnPlaneWinding = false, bool updateLights = true, bool makeFacePlanes = true );
brush_t * Brush_Clone (brush_t *b);
brush_t * Brush_FullClone(brush_t *b);
brush_t * Brush_Create (idVec3 mins, idVec3 maxs, texdef_t *texdef);
void Brush_Draw( brush_t *b, bool bSelected = false);
void Brush_DrawXY(brush_t *b, int nViewType, bool bSelected = false, bool ignoreViewType = false);
void Brush_SplitBrushByFace (brush_t *in, face_t *f, brush_t **front, brush_t **back);
void Brush_SelectFaceForDragging (brush_t *b, face_t *f, bool shear);
void Brush_SetTexture (brush_t *b, texdef_t *texdef, brushprimit_texdef_t *brushprimit_texdef, bool bFitScale = false);
void Brush_SideSelect (brush_t *b, idVec3 origin, idVec3 dir, bool shear);
void Brush_SnapToGrid(brush_t *pb);
void Brush_Rotate(brush_t *b, idVec3 vAngle, idVec3 vOrigin, bool bBuild = true);
brush_t* Brush_Alloc();
void Brush_Free(brush_t* b, bool bRemoveNode = true);
int Brush_MemorySize(brush_t* b);
void Brush_MakeSided(int sides);
void Brush_MakeSidedCone(int sides);
void Brush_Move(brush_t* b, const idVec3 move, bool bSnap = true, bool updateOrigin = true);
int Brush_MoveVertex(brush_t* b, const idVec3& vertex, const idVec3& delta, idVec3& end, bool bSnap);
void Brush_ResetFaceOriginals(brush_t* b);
brush_t* Brush_Parse(const idVec3 origin);
face_t* Brush_Ray(idVec3 origin, idVec3 dir, brush_t* b, float* dist, bool testPrimitive = false);
void Brush_RemoveFromList(brush_t* b);
void Brush_AddToList(brush_t* b, brush_t* list);
void Brush_Build(brush_t* b, bool bSnap = true, bool bMarkMap = true, bool bConvert = false, bool updateLights = true);
void Brush_BuildWindings(brush_t* b, bool bSnap = true, bool keepOnPlaneWinding = false, bool updateLights = true, bool makeFacePlanes = true);
brush_t* Brush_Clone(brush_t* b);
brush_t* Brush_FullClone(brush_t* b);
brush_t* Brush_Create(idVec3 mins, idVec3 maxs, texdef_t* texdef);
brush_t* Brush_CreateFaceExtrusion(brush_t* sourceBrush, face_t* sourceFace, float distance);
void Brush_Draw(brush_t* b, bool bSelected = false);
void Brush_DrawXY(brush_t* b, int nViewType, bool bSelected = false, bool ignoreViewType = false);
void Brush_SplitBrushByFace(brush_t* in, face_t* f, brush_t** front, brush_t** back);
void Brush_SelectFaceForDragging(brush_t* b, face_t* f, bool shear);
void Brush_SetTexture(brush_t* b, texdef_t* texdef, brushprimit_texdef_t* brushprimit_texdef, bool bFitScale = false);
void Brush_SideSelect(brush_t* b, idVec3 origin, idVec3 dir, bool shear);
void Brush_SnapToGrid(brush_t* pb);
void Brush_Rotate(brush_t* b, idVec3 vAngle, idVec3 vOrigin, bool bBuild = true);
void Brush_MakeSidedSphere(int sides);
void Brush_Write (brush_t *b, FILE *f, const idVec3 &origin, bool newFormat);
void Brush_Write (brush_t *b, CMemFile* pMemFile, const idVec3 &origin, bool NewFormat);
void Brush_RemoveEmptyFaces ( brush_t *b );
idWinding * Brush_MakeFaceWinding (brush_t *b, face_t *face, bool keepOnPlaneWinding = false);
void Brush_SetTextureName(brush_t *b, const char *name);
void Brush_Write(brush_t* b, FILE* f, const idVec3& origin, bool newFormat);
void Brush_Write(brush_t* b, CMemFile* pMemFile, const idVec3& origin, bool NewFormat);
void Brush_RemoveEmptyFaces(brush_t* b);
idWinding* Brush_MakeFaceWinding(brush_t* b, face_t* face, bool keepOnPlaneWinding = false);
void Brush_SetTextureName(brush_t* b, const char* name);
void Brush_Print(brush_t* b);
void Brush_FitTexture( brush_t *b, float height, float width );
void Brush_SetEpair(brush_t *b, const char *pKey, const char *pValue);
const char *Brush_GetKeyValue(brush_t *b, const char *pKey);
const char *Brush_Name(brush_t *b);
void Brush_RebuildBrush(brush_t *b, idVec3 vMins, idVec3 vMaxs, bool patch = true);
void Brush_GetBounds( brush_t *b, idBounds &bo );
void Brush_FitTexture(brush_t* b, float height, float width);
void Brush_SetEpair(brush_t* b, const char* pKey, const char* pValue);
const char* Brush_GetKeyValue(brush_t* b, const char* pKey);
const char* Brush_Name(brush_t* b);
void Brush_RebuildBrush(brush_t* b, idVec3 vMins, idVec3 vMaxs, bool patch = true);
void Brush_GetBounds(brush_t* b, idBounds& bo);
face_t * Face_Alloc( void );
void Face_Free( face_t *f );
face_t * Face_Clone (face_t *f);
void Face_MakePlane (face_t *f);
void Face_Draw( face_t *face );
void Face_TextureVectors (face_t *f, float STfromXYZ[2][4]);
void Face_FitTexture( face_t * face, float height, float width );
void SetFaceTexdef (brush_t *b, face_t *f, texdef_t *texdef, brushprimit_texdef_t *brushprimit_texdef, bool bFitScale = false);
face_t* Face_Alloc(void);
void Face_Free(face_t* f);
face_t* Face_Clone(face_t* f);
void Face_MakePlane(face_t* f);
void Face_Draw(face_t* face);
void Face_TextureVectors(face_t* f, float STfromXYZ[2][4]);
void Face_FitTexture(face_t* face, float height, float width);
void SetFaceTexdef(brush_t* b, face_t* f, texdef_t* texdef, brushprimit_texdef_t* brushprimit_texdef, bool bFitScale = false);
int AddPlanept (idVec3 *f);
int AddPlanept(idVec3* f);