From b70665c103f650c891caf6666c62ba068dd9743a Mon Sep 17 00:00:00 2001 From: "DESKTOP-ADL4CP8\\user" Date: Tue, 25 Nov 2025 19:58:31 +0200 Subject: [PATCH 1/7] feature(WorldBuilder): add all operation modes to single brush tool --- .../Tools/WorldBuilder/include/BrushTool.h | 42 +++ .../Tools/WorldBuilder/include/brushoptions.h | 20 +- .../Code/Tools/WorldBuilder/res/resource.h | 3 + .../Code/Tools/WorldBuilder/src/BrushTool.cpp | 346 +++++++++++++++++- .../Tools/WorldBuilder/src/WorldBuilder.cpp | 4 +- .../Tools/WorldBuilder/src/brushoptions.cpp | 153 ++++++++ 6 files changed, 552 insertions(+), 16 deletions(-) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h b/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h index 7c767c82bb..e3e5df67ff 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h @@ -31,14 +31,40 @@ class WorldHeightMapEdit; /// Height brush tool. class BrushTool : public Tool { +public: + enum { + MIN_RAISE_LOWER = 1, + MAX_RAISE_LOWER = 21, + MIN_SMOOTH_RADIUS = 1, + MAX_SMOOTH_RADIUS = 5, + MIN_SMOOTH_RATE = 1, + MAX_SMOOTH_RATE = 10 + }; + + enum EBrushMode { + BRUSH_MODE_RAISE, + BRUSH_MODE_LOWER, + BRUSH_MODE_SET, + BRUSH_MODE_SMOOTH + }; + protected: + enum {MIN_DELAY_TIME = 60}; + WorldHeightMapEdit *m_htMapEditCopy; ///< ref counted. WorldHeightMapEdit *m_htMapFeatherCopy; ///< ref counted. + WorldHeightMapEdit *m_htMapRateCopy; ///< ref counted (smooth mode). static Int m_brushWidth; static Int m_brushFeather; static Bool m_brushSquare; static Int m_brushHeight; + static Int m_raiseLowerAmount; + static Int m_smoothRadius; + static Int m_smoothRate; + + EBrushMode m_activeMode; + Int m_lastMoveTime; public: BrushTool(void); @@ -48,9 +74,19 @@ class BrushTool : public Tool static Int getWidth(void) {return m_brushWidth;}; /// MAX_RAISE_LOWER) amount = MAX_RAISE_LOWER; + if (m_raiseLowerAmount != amount) { + m_raiseLowerAmount = amount; + BrushOptions::setRaiseLowerAmount(amount); + } +}; + +void BrushTool::setSmoothRadius(Int radius) +{ + if (radius < MIN_SMOOTH_RADIUS) radius = MIN_SMOOTH_RADIUS; + if (radius > MAX_SMOOTH_RADIUS) radius = MAX_SMOOTH_RADIUS; + if (m_smoothRadius != radius) { + m_smoothRadius = radius; + BrushOptions::setSmoothRadius(radius); + } +}; + +void BrushTool::setSmoothRate(Int rate) +{ + if (rate < MIN_SMOOTH_RATE) rate = MIN_SMOOTH_RATE; + if (rate > MAX_SMOOTH_RATE) rate = MAX_SMOOTH_RATE; + if (m_smoothRate != rate) { + m_smoothRate = rate; + BrushOptions::setSmoothRate(rate); + } +}; + /// Set the brush width and notify the height options panel of the change. void BrushTool::setWidth(Int width) { @@ -109,14 +151,27 @@ void BrushTool::mouseDown(TTrackingMode m, CPoint viewPt, WbView* pView, CWorldB { if (m != TRACK_L) return; -// WorldHeightMapEdit *pMap = pDoc->GetHeightMap(); - // just in case, release it. + m_activeMode = determineBrushMode(); + REF_PTR_RELEASE(m_htMapEditCopy); m_htMapEditCopy = pDoc->GetHeightMap()->duplicate(); - m_prevXIndex = -1; - m_prevYIndex = -1; REF_PTR_RELEASE(m_htMapFeatherCopy); m_htMapFeatherCopy = m_htMapEditCopy->duplicate(); + REF_PTR_RELEASE(m_htMapRateCopy); + + if (m_activeMode == BRUSH_MODE_SMOOTH) { + m_htMapRateCopy = m_htMapEditCopy->duplicate(); + resetSmoothRateBuffer(); + } + + m_prevXIndex = -1; + m_prevYIndex = -1; + + m_lastMoveTime = ::GetTickCount(); + if (m_activeMode == BRUSH_MODE_RAISE || m_activeMode == BRUSH_MODE_LOWER) { + m_lastMoveTime -= MIN_DELAY_TIME + 1; // Fire immediately for raise/lower. + } + mouseMoved(m, viewPt, pView, pDoc); } @@ -132,6 +187,7 @@ void BrushTool::mouseUp(TTrackingMode m, CPoint viewPt, WbView* pView, CWorldBui REF_PTR_RELEASE(pUndo); // belongs to pDoc now. REF_PTR_RELEASE(m_htMapEditCopy); REF_PTR_RELEASE(m_htMapFeatherCopy); + REF_PTR_RELEASE(m_htMapRateCopy); } /// Execute the tool. @@ -146,20 +202,65 @@ void BrushTool::mouseMoved(TTrackingMode m, CPoint viewPt, WbView* pView, CWorld pView->viewToDocCoords(viewPt, &cpt); - int brushWidth = m_brushWidth; - if (m_brushFeather>0) { - brushWidth += 2*m_brushFeather; - } - brushWidth += 2; - CPoint ndx; getCenterIndex(&cpt, m_brushWidth, &ndx, pDoc); - if (m_prevXIndex == ndx.x && m_prevYIndex == ndx.y) return; + Bool skipDuplicateCell = (m_activeMode == BRUSH_MODE_SET); + if (skipDuplicateCell && m_prevXIndex == ndx.x && m_prevYIndex == ndx.y) return; m_prevXIndex = ndx.x; m_prevYIndex = ndx.y; + switch (m_activeMode) { + case BRUSH_MODE_RAISE: + case BRUSH_MODE_LOWER: { + Int brushWidth = m_brushWidth; + if (m_brushFeather>0) { + brushWidth += 2*m_brushFeather; + } + brushWidth += 2; + applyRaiseLowerBrush(ndx, brushWidth, (m_activeMode == BRUSH_MODE_RAISE), pDoc); + break; + } + case BRUSH_MODE_SET: { + Int brushWidth = m_brushWidth; + if (m_brushFeather>0) { + brushWidth += 2*m_brushFeather; + } + brushWidth += 2; + applySetHeightBrush(ndx, brushWidth, pDoc); + break; + } + case BRUSH_MODE_SMOOTH: { + Int brushWidth = m_brushWidth; + if (brushWidth < 1) brushWidth = 1; + applySmoothBrush(ndx, brushWidth, pDoc); + break; + } + } +} + +BrushTool::EBrushMode BrushTool::determineBrushMode() const +{ + Bool ctrl = (0x8000 & ::GetAsyncKeyState(VK_CONTROL)) != 0; + Bool shift = (0x8000 & ::GetAsyncKeyState(VK_SHIFT)) != 0; + + if (ctrl) { + if (shift) { + return BRUSH_MODE_SMOOTH; + } + return BRUSH_MODE_SET; + } + if (shift) { + return BRUSH_MODE_LOWER; + } + return BRUSH_MODE_RAISE; +} + +void BrushTool::applySetHeightBrush(const CPoint &ndx, Int brushWidth, CWorldBuilderDoc *pDoc) +{ + if (!m_htMapEditCopy || !m_htMapFeatherCopy) return; + int sub = brushWidth/2; int add = brushWidth-sub; @@ -179,8 +280,8 @@ void BrushTool::mouseMoved(TTrackingMode m, CPoint viewPt, WbView* pView, CWorld blendFactor = calcRoundBlendFactor(ndx, i, j, m_brushWidth, m_brushFeather); } Int curHeight = m_htMapFeatherCopy->getHeight(i,j); - float fNewHeight = blendFactor*m_brushHeight+((1.0f-blendFactor)*curHeight) ; - Int newHeight = floor(fNewHeight+0.5); + float fNewHeight = blendFactor*m_brushHeight+((1.0f-blendFactor)*curHeight); + Int newHeight = floor(fNewHeight+0.5f); if (m_brushHeight > curHeight) { if (m_htMapEditCopy->getHeight(i,j)>newHeight) { newHeight = m_htMapEditCopy->getHeight(i,j); @@ -193,10 +294,229 @@ void BrushTool::mouseMoved(TTrackingMode m, CPoint viewPt, WbView* pView, CWorld m_htMapEditCopy->setHeight(i, j, newHeight); } } + + IRegion2D partialRange; + partialRange.lo.x = ndx.x - brushWidth; + partialRange.hi.x = ndx.x + brushWidth; + partialRange.lo.y = ndx.y - brushWidth; + partialRange.hi.y = ndx.y + brushWidth; + pDoc->updateHeightMap(m_htMapEditCopy, true, partialRange); +} + +void BrushTool::applyRaiseLowerBrush(const CPoint &ndx, Int brushWidth, Bool raising, CWorldBuilderDoc *pDoc) +{ + if (!m_htMapEditCopy || !m_htMapFeatherCopy) return; + + Int curTime = ::GetTickCount(); + Int deltaTime = curTime - m_lastMoveTime; + if (deltaTime < MIN_DELAY_TIME) return; + m_lastMoveTime = curTime; + + int sub = brushWidth/2; + int add = brushWidth-sub; + + Int htDelta = raising?m_raiseLowerAmount:-m_raiseLowerAmount; + Int i, j; + for (i=ndx.x-sub; i=m_htMapEditCopy->getXExtent()) { + continue; + } + for (j=ndx.y-sub; j=m_htMapEditCopy->getYExtent()) { + continue; + } + Real blendFactor; + if (m_brushSquare) { + blendFactor = calcSquareBlendFactor(ndx, i, j, m_brushWidth, m_brushFeather); + } else { + blendFactor = calcRoundBlendFactor(ndx, i, j, m_brushWidth, m_brushFeather); + } + Int curHeight = m_htMapEditCopy->getHeight(i,j); + float fNewHeight = (blendFactor*(htDelta+curHeight))+((1.0f-blendFactor)*curHeight); + Int newHeight = floor(fNewHeight+0.5f); + + if (newHeight < m_htMapFeatherCopy->getMinHeightValue()) newHeight = m_htMapFeatherCopy->getMinHeightValue(); + if (newHeight > m_htMapFeatherCopy->getMaxHeightValue()) newHeight = m_htMapFeatherCopy->getMaxHeightValue(); + + m_htMapEditCopy->setHeight(i, j, newHeight); + pDoc->invalCell(i, j); + } + } + + IRegion2D partialRange; + partialRange.lo.x = ndx.x - brushWidth; + partialRange.hi.x = ndx.x + brushWidth; + partialRange.lo.y = ndx.y - brushWidth; + partialRange.hi.y = ndx.y + brushWidth; + pDoc->updateHeightMap(m_htMapEditCopy, true, partialRange); +} + +void BrushTool::applySmoothBrush(const CPoint &ndx, Int brushWidth, CWorldBuilderDoc *pDoc) +{ + if (!m_htMapEditCopy || !m_htMapFeatherCopy || !m_htMapRateCopy) return; + + int sub = brushWidth/2; + int add = brushWidth-sub; + + Int i, j; + Bool redoRate = false; + + for (i= ndx.x-sub; i< ndx.x+add; i++) { + if (i<0 || i>=m_htMapEditCopy->getXExtent()) { + continue; + } + for (j=ndx.y-sub; j=m_htMapEditCopy->getYExtent()) { + continue; + } + Real blendFactor = calcRoundBlendFactor(ndx, i, j, brushWidth, 0); + + if (blendFactor > 0.0f) { + Int rate = m_htMapRateCopy->getHeight(i, j); + rate += blendFactor * m_smoothRate*5; + if (rate>255) { + rate = 255; + redoRate = true; + } + m_htMapRateCopy->setHeight(i,j,rate); + + Int total=0; + Real numSamples=0; + Int ii, jj; + Int radius = m_smoothRadius; + if (radiusMAX_SMOOTH_RADIUS) radius = MAX_SMOOTH_RADIUS; + for (ii = i-radius; ii < i+radius+1; ii++) { + for (jj = j-radius; jjradius) { + factor = 0; + } else { + factor = 1.0f - (dist-1)/radius; + } + } + int iNdx = ii; + if (iNdx<0) iNdx = 1; + if (iNdx >=m_htMapEditCopy->getXExtent()) { + iNdx = m_htMapEditCopy->getXExtent()-1; + } + int jNdx = jj; + if (jNdx<0) jNdx = 1; + if (jNdx >=m_htMapEditCopy->getYExtent()) { + jNdx = m_htMapEditCopy->getYExtent()-1; + } + total += m_htMapFeatherCopy->getHeight(iNdx, jNdx); + numSamples+=1; + } + } + total = floor((total/numSamples)); + UnsignedByte origHeight = m_htMapFeatherCopy->getHeight(i, j); + float rateF = rate/255.0f; + total = floor(origHeight*(1.0f-rateF) + total*rateF + 0.5f); + m_htMapEditCopy->setHeight(i, j, total); + pDoc->invalCell(i, j); + } + } + } + IRegion2D partialRange; partialRange.lo.x = ndx.x - brushWidth; partialRange.hi.x = ndx.x + brushWidth; partialRange.lo.y = ndx.y - brushWidth; partialRange.hi.y = ndx.y + brushWidth; pDoc->updateHeightMap(m_htMapEditCopy, true, partialRange); + + if (redoRate) { + resetSmoothRateBuffer(); + } +} + +void BrushTool::resetSmoothRateBuffer() +{ + if (!m_htMapRateCopy || !m_htMapFeatherCopy || !m_htMapEditCopy) return; + + Int size = m_htMapRateCopy->getXExtent() * m_htMapRateCopy->getYExtent(); + UnsignedByte *pRate = m_htMapRateCopy->getDataPtr(); + UnsignedByte *pFeather = m_htMapFeatherCopy->getDataPtr(); + UnsignedByte *pEdit = m_htMapEditCopy->getDataPtr(); + for (Int idx=0; idx 0) { + primaryBuf[0] = '\0'; + } + if (secondaryBuf && secondaryBufSize > 0) { + secondaryBuf[0] = '\0'; + } + + Bool shiftDown = (0x8000 & ::GetAsyncKeyState(VK_SHIFT)) != 0; + Bool ctrlDown = (0x8000 & ::GetAsyncKeyState(VK_CONTROL)) != 0; + + EBrushMode mode = getModeFromModifiers(shiftDown, ctrlDown); + const char *modeLabel = getModeDisplayName(mode); + + if (primaryBuf && primaryBufSize > 0) { + snprintf(primaryBuf, primaryBufSize, "Brush Mode: %s", modeLabel); + } + + if (secondaryBuf && secondaryBufSize > 0) { + const char *defaultPrefix = (!shiftDown && !ctrlDown) ? "[" : ""; + const char *defaultSuffix = (!shiftDown && !ctrlDown) ? "]" : ""; + const char *shiftPrefix = (shiftDown && !ctrlDown) ? "[" : ""; + const char *shiftSuffix = (shiftDown && !ctrlDown) ? "]" : ""; + const char *ctrlPrefix = (!shiftDown && ctrlDown) ? "[" : ""; + const char *ctrlSuffix = (!shiftDown && ctrlDown) ? "]" : ""; + const char *ctrlShiftPrefix = (shiftDown && ctrlDown) ? "[" : ""; + const char *ctrlShiftSuffix = (shiftDown && ctrlDown) ? "]" : ""; + + snprintf(secondaryBuf, secondaryBufSize, + "%sDefault: Raise%s %sShift: Lower%s %sCtrl: Set%s %sCtrl+Shift: Smooth%s", + defaultPrefix, defaultSuffix, + shiftPrefix, shiftSuffix, + ctrlPrefix, ctrlSuffix, + ctrlShiftPrefix, ctrlShiftSuffix); + } } diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp index d4459af550..81ebb93205 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp @@ -571,8 +571,8 @@ void CWorldBuilderApp::updateCurTool(Bool forceHand) } else if (0x8000 & ::GetAsyncKeyState(VK_MENU)) { // Alt key gives eyedropper. m_curTool = &m_eyedropperTool; - } else if (0x8000 & ::GetAsyncKeyState(VK_CONTROL)) { - // Control key gives pointer. + } else if (0x8000 & ::GetAsyncKeyState('W')) { + // W key gives pointer. m_curTool = &m_pointerTool; } else { // Else the tool selected in the tool palette. diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/brushoptions.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/brushoptions.cpp index f8c50586f1..d8c23682b3 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/brushoptions.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/brushoptions.cpp @@ -30,6 +30,9 @@ BrushOptions *BrushOptions::m_staticThis = NULL; Int BrushOptions::m_currentWidth = 0; Int BrushOptions::m_currentHeight = 0; Int BrushOptions::m_currentFeather = 0; +Int BrushOptions::m_currentRaiseLower = BrushTool::MIN_RAISE_LOWER; +Int BrushOptions::m_currentSmoothRadius = BrushTool::MIN_SMOOTH_RADIUS; +Int BrushOptions::m_currentSmoothRate = BrushTool::MIN_SMOOTH_RATE; ///////////////////////////////////////////////////////////////////////////// /// BrushOptions dialog trivial construstor - Create does the real work. @@ -87,6 +90,42 @@ void BrushOptions::setHeight(Int height) } } +void BrushOptions::setRaiseLowerAmount(Int amount) +{ + char buffer[12]; + snprintf(buffer, ARRAY_SIZE(buffer), "%d", amount); + m_currentRaiseLower = amount; + if (m_staticThis && !m_staticThis->m_updating) { + CWnd *pEdit = m_staticThis->GetDlgItem(IDC_RAISELOWER_EDIT); + if (pEdit) pEdit->SetWindowText(buffer); + snprintf(buffer, ARRAY_SIZE(buffer), "%.1f FEET.", m_currentRaiseLower*MAP_HEIGHT_SCALE); + pEdit = m_staticThis->GetDlgItem(IDC_RAISELOWER_LABEL); + if (pEdit) pEdit->SetWindowText(buffer); + } +} + +void BrushOptions::setSmoothRadius(Int radius) +{ + CString buf; + buf.Format("%d", radius); + m_currentSmoothRadius = radius; + if (m_staticThis && !m_staticThis->m_updating) { + CWnd *pEdit = m_staticThis->GetDlgItem(IDC_RADIUS_EDIT); + if (pEdit) pEdit->SetWindowText(buf); + } +} + +void BrushOptions::setSmoothRate(Int rate) +{ + CString buf; + buf.Format("%d", rate); + m_currentSmoothRate = rate; + if (m_staticThis && !m_staticThis->m_updating) { + CWnd *pEdit = m_staticThis->GetDlgItem(IDC_RATE_EDIT); + if (pEdit) pEdit->SetWindowText(buf); + } +} + ///////////////////////////////////////////////////////////////////////////// @@ -103,6 +142,9 @@ BOOL BrushOptions::OnInitDialog() m_brushWidthPopup.SetupPopSliderButton(this, IDC_SIZE_POPUP, this); m_brushFeatherPopup.SetupPopSliderButton(this, IDC_FEATHER_POPUP, this); m_brushHeightPopup.SetupPopSliderButton(this, IDC_HEIGHT_POPUP, this); + m_raiseLowerPopup.SetupPopSliderButton(this, IDC_RAISELOWER_POPUP, this); + m_smoothRadiusPopup.SetupPopSliderButton(this, IDC_RADIUS_POPUP, this); + m_smoothRatePopup.SetupPopSliderButton(this, IDC_RATE_POPUP, this); m_staticThis = this; @@ -110,6 +152,9 @@ BOOL BrushOptions::OnInitDialog() setFeather(m_currentFeather); setWidth(m_currentWidth); setHeight(m_currentHeight); + setRaiseLowerAmount(m_currentRaiseLower); + setSmoothRadius(m_currentSmoothRadius); + setSmoothRate(m_currentSmoothRate); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } @@ -183,6 +228,60 @@ void BrushOptions::OnChangeHeightEdit() } } +void BrushOptions::OnChangeRaiseLowerEdit() +{ + if (m_updating) return; + CWnd *pEdit = m_staticThis->GetDlgItem(IDC_RAISELOWER_EDIT); + char buffer[_MAX_PATH]; + if (pEdit) { + pEdit->GetWindowText(buffer, sizeof(buffer)); + Int amount; + m_updating = true; + if (1==sscanf(buffer, "%d", &amount)) { + m_currentRaiseLower = amount; + BrushTool::setRaiseLowerAmount(m_currentRaiseLower); + snprintf(buffer, ARRAY_SIZE(buffer), "%.1f FEET.", m_currentRaiseLower*MAP_HEIGHT_SCALE); + pEdit = m_staticThis->GetDlgItem(IDC_RAISELOWER_LABEL); + if (pEdit) pEdit->SetWindowText(buffer); + } + m_updating = false; + } +} + +void BrushOptions::OnChangeSmoothRadiusEdit() +{ + if (m_updating) return; + CWnd *pEdit = m_staticThis->GetDlgItem(IDC_RADIUS_EDIT); + char buffer[_MAX_PATH]; + if (pEdit) { + pEdit->GetWindowText(buffer, sizeof(buffer)); + Int radius; + m_updating = true; + if (1==sscanf(buffer, "%d", &radius)) { + m_currentSmoothRadius = radius; + BrushTool::setSmoothRadius(m_currentSmoothRadius); + } + m_updating = false; + } +} + +void BrushOptions::OnChangeSmoothRateEdit() +{ + if (m_updating) return; + CWnd *pEdit = m_staticThis->GetDlgItem(IDC_RATE_EDIT); + char buffer[_MAX_PATH]; + if (pEdit) { + pEdit->GetWindowText(buffer, sizeof(buffer)); + Int rate; + m_updating = true; + if (1==sscanf(buffer, "%d", &rate)) { + m_currentSmoothRate = rate; + BrushTool::setSmoothRate(m_currentSmoothRate); + } + m_updating = false; + } +} + void BrushOptions::GetPopSliderInfo(const long sliderID, long *pMin, long *pMax, long *pLineSize, long *pInitial) { switch (sliderID) { @@ -208,6 +307,27 @@ void BrushOptions::GetPopSliderInfo(const long sliderID, long *pMin, long *pMax, *pLineSize = 1; break; + case IDC_RAISELOWER_POPUP: + *pMin = BrushTool::MIN_RAISE_LOWER; + *pMax = BrushTool::MAX_RAISE_LOWER; + *pInitial = m_currentRaiseLower; + *pLineSize = 1; + break; + + case IDC_RADIUS_POPUP: + *pMin = BrushTool::MIN_SMOOTH_RADIUS; + *pMax = BrushTool::MAX_SMOOTH_RADIUS; + *pInitial = m_currentSmoothRadius; + *pLineSize = 1; + break; + + case IDC_RATE_POPUP: + *pMin = BrushTool::MIN_SMOOTH_RATE; + *pMax = BrushTool::MAX_SMOOTH_RATE; + *pInitial = m_currentSmoothRate; + *pLineSize = 1; + break; + default: // uh-oh! DEBUG_CRASH(("Slider message from unknown control")); @@ -245,6 +365,30 @@ void BrushOptions::PopSliderChanged(const long sliderID, long theVal) BrushTool::setFeather(m_currentFeather); break; + case IDC_RAISELOWER_POPUP: + m_currentRaiseLower = theVal; + str.Format("%d", m_currentRaiseLower); + pEdit = m_staticThis->GetDlgItem(IDC_RAISELOWER_EDIT); + if (pEdit) pEdit->SetWindowText(str); + BrushTool::setRaiseLowerAmount(m_currentRaiseLower); + break; + + case IDC_RADIUS_POPUP: + m_currentSmoothRadius = theVal; + str.Format("%d", m_currentSmoothRadius); + pEdit = m_staticThis->GetDlgItem(IDC_RADIUS_EDIT); + if (pEdit) pEdit->SetWindowText(str); + BrushTool::setSmoothRadius(m_currentSmoothRadius); + break; + + case IDC_RATE_POPUP: + m_currentSmoothRate = theVal; + str.Format("%d", m_currentSmoothRate); + pEdit = m_staticThis->GetDlgItem(IDC_RATE_EDIT); + if (pEdit) pEdit->SetWindowText(str); + BrushTool::setSmoothRate(m_currentSmoothRate); + break; + default: // uh-oh! DEBUG_CRASH(("Slider message from unknown control")); @@ -261,6 +405,12 @@ void BrushOptions::PopSliderFinished(const long sliderID, long theVal) break; case IDC_FEATHER_POPUP: break; + case IDC_RAISELOWER_POPUP: + break; + case IDC_RADIUS_POPUP: + break; + case IDC_RATE_POPUP: + break; default: // uh-oh! @@ -276,6 +426,9 @@ BEGIN_MESSAGE_MAP(BrushOptions, COptionsPanel) ON_EN_CHANGE(IDC_FEATHER_EDIT, OnChangeFeatherEdit) ON_EN_CHANGE(IDC_SIZE_EDIT, OnChangeSizeEdit) ON_EN_CHANGE(IDC_HEIGHT_EDIT, OnChangeHeightEdit) + ON_EN_CHANGE(IDC_RAISELOWER_EDIT, OnChangeRaiseLowerEdit) + ON_EN_CHANGE(IDC_RADIUS_EDIT, OnChangeSmoothRadiusEdit) + ON_EN_CHANGE(IDC_RATE_EDIT, OnChangeSmoothRateEdit) ON_WM_HSCROLL() //}}AFX_MSG_MAP END_MESSAGE_MAP() From 02ac2208e35433d8d592ffe78671fce5b627ac35 Mon Sep 17 00:00:00 2001 From: "DESKTOP-ADL4CP8\\user" Date: Tue, 25 Nov 2025 20:06:41 +0200 Subject: [PATCH 2/7] feature(worldbuilder): add ability to change feather and brush size with scroll wheel --- .../Code/Tools/WorldBuilder/include/wbview.h | 5 +- .../Code/Tools/WorldBuilder/src/wbview.cpp | 61 +++++++++++++++++++ .../Code/Tools/WorldBuilder/src/wbview3d.cpp | 6 ++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview.h b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview.h index 60f9cdd642..5f4ba0074b 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview.h @@ -241,10 +241,13 @@ class WbView : public CView afx_msg void OnShowTerrain(); afx_msg void OnUpdateShowTerrain(CCmdUI* pCmdUI); afx_msg int OnCreate(LPCREATESTRUCT lpcs); - + afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint point); //}}AFX_MSG DECLARE_MESSAGE_MAP() + +protected: + Bool handleBrushMouseWheel(UINT nFlags, short zDelta); }; ///////////////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview.cpp index c0e65b35e2..7529ffdedb 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview.cpp @@ -35,6 +35,7 @@ #include "playerlistdlg.h" #include "teamsdialog.h" #include "LayersList.h" +#include "BrushTool.h" Bool WbView::m_snapToGrid = false; @@ -139,6 +140,7 @@ BEGIN_MESSAGE_MAP(WbView, CView) ON_COMMAND(ID_VIEW_SHOW_TERRAIN, OnShowTerrain) ON_UPDATE_COMMAND_UI(ID_VIEW_SHOW_TERRAIN, OnUpdateShowTerrain) ON_WM_CREATE() + ON_WM_MOUSEWHEEL() //}}AFX_MSG_MAP END_MESSAGE_MAP() @@ -1083,3 +1085,62 @@ void WbView::rulerFeedbackInfo(Coord3D &point1, Coord3D &point2, Real dist) m_rulerPoints[1] = point2; m_rulerLength = dist; } + +// ---------------------------------------------------------------------------- +BOOL WbView::OnMouseWheel(UINT nFlags, short zDelta, CPoint point) +{ + // Check if brush tool should handle this + if (handleBrushMouseWheel(nFlags, zDelta)) { + return TRUE; + } + + // Default behavior: pass to base class for scrolling + return CView::OnMouseWheel(nFlags, zDelta, point); +} + +// ---------------------------------------------------------------------------- +Bool WbView::handleBrushMouseWheel(UINT nFlags, short zDelta) +{ + // Only handle if brush tool is active + Tool *pCurTool = WbApp()->getCurTool(); + if (pCurTool && dynamic_cast(pCurTool) != NULL) { + Bool shiftDown = (nFlags & MK_SHIFT) != 0; + Bool ctrlDown = (nFlags & MK_CONTROL) != 0; + + if (shiftDown && !ctrlDown) { + // Shift + Scroll Wheel -> Adjust brush width + Int currentWidth = BrushTool::getWidth(); + Int delta = zDelta > 0 ? 1 : -1; + Int newWidth = currentWidth + delta; + // Clamp to reasonable range (1-100) + if (newWidth < 1) newWidth = 1; + if (newWidth > 100) newWidth = 100; + BrushTool::setWidth(newWidth); + + // Update status bar + CString statusText; + statusText.Format("Brush Width: %d", newWidth); + CMainFrame::GetMainFrame()->SetMessageText(statusText); + + return TRUE; + } else if (ctrlDown && !shiftDown) { + // Control + Scroll Wheel -> Adjust brush feather + Int currentFeather = BrushTool::getFeather(); + Int delta = zDelta > 0 ? 1 : -1; + Int newFeather = currentFeather + delta; + // Clamp to reasonable range (0-100) + if (newFeather < 0) newFeather = 0; + if (newFeather > 100) newFeather = 100; + BrushTool::setFeather(newFeather); + + // Update status bar + CString statusText; + statusText.Format("Brush Feather: %d", newFeather); + CMainFrame::GetMainFrame()->SetMessageText(statusText); + + return TRUE; + } + } + + return FALSE; +} diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp index c33edb6fee..1f398f58e2 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -2654,6 +2654,12 @@ void WbView3d::OnSize(UINT nType, int cx, int cy) // ---------------------------------------------------------------------------- BOOL WbView3d::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { + // Check if brush tool should handle this first + if (handleBrushMouseWheel(nFlags, zDelta)) { + return TRUE; + } + + // Default behavior: camera zoom if (m_trackingMode == TRACK_NONE) { //WST 11/21/02 New Triple speed camera zoom request by designers From a6110ada72c379886c3a98f5e474c104a3ac9dbb Mon Sep 17 00:00:00 2001 From: "DESKTOP-ADL4CP8\\user" Date: Tue, 25 Nov 2025 21:52:35 +0200 Subject: [PATCH 3/7] feature(worldbuilder): Add on-screen context hint for brush use --- .../Tools/WorldBuilder/include/DrawObject.h | 1 + .../WorldBuilder/include/WorldBuilderView.h | 9 + .../Tools/WorldBuilder/include/wbview3d.h | 8 + .../Tools/WorldBuilder/res/WorldBuilder.rc | 33 +++- .../Code/Tools/WorldBuilder/res/resource.h | 1 + .../WorldBuilder/src/WorldBuilderView.cpp | 167 +++++++++++++++- .../Code/Tools/WorldBuilder/src/wbview3d.cpp | 185 +++++++++++++++++- 7 files changed, 394 insertions(+), 10 deletions(-) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/DrawObject.h b/GeneralsMD/Code/Tools/WorldBuilder/include/DrawObject.h index 77ea5426b1..6581655295 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/DrawObject.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/DrawObject.h @@ -96,6 +96,7 @@ class DrawObject : public RenderObjClass static void enableFeedback(void) {m_disableFeedback = false;}; static Bool isFeedbackEnabled(void) { return !m_disableFeedback;}; static void setFeedbackPos(Coord3D pos); + static Coord3D getFeedbackPos() { return m_feedbackPoint; } static void setWaypointDragFeedback(const Coord3D &start, const Coord3D &end); static void setRampFeedbackParms(const Coord3D *start, const Coord3D *end, Real rampWidth); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h b/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h index 919d8522bd..f8eb4a6bc6 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h @@ -73,6 +73,12 @@ class CWorldBuilderView : public WbView CPoint m_scrollMin; ///< The minimum scrollbar positions. CPoint m_scrollMax; ///< The maximum scroll bar positions. + // Brush mode hint flicker reduction + Int m_lastBrushMode; ///< Last brush mode drawn (for flicker reduction) + CRect m_lastHintRect; ///< Last hint rectangle drawn (for flicker reduction) + CPoint m_lastHintPos; ///< Last hint position (for flicker reduction) + Bool m_hintDrawnThisPaint; ///< Flag to prevent double-drawing in same paint cycle + protected: /// Draw a texture bitmap in a rectangle in the dc. @@ -98,6 +104,9 @@ class CWorldBuilderView : public WbView /// Draw the object's icon in the dc at a given point. void drawObjectInView(CDC *pDc, MapObject *pMapObj); + /// Draw brush mode hint on canvas + void drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn = NULL); + public: /// Get the current draw size in pixels in the 2d window of one height map cell. Int getCellSize(void) {return m_cellSize;} diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h index 95d71082e5..7dbfbc1a75 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h @@ -93,6 +93,7 @@ class WbView3d : public WbView, public DX8_CleanupHook afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnPaint(); afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); afx_msg void OnTimer(UINT nIDEvent); afx_msg void OnDestroy(); @@ -216,6 +217,12 @@ class WbView3d : public WbView, public DX8_CleanupHook Int m_pickPixels; Int m_partialMapSize; + // Brush mode hint flicker reduction + Int m_lastBrushMode; ///< Last brush mode drawn (for flicker reduction) + RECT m_lastHintRect; ///< Last hint rectangle drawn (for flicker reduction) + CPoint m_lastHintPos; ///< Last hint position (for flicker reduction) + Bool m_hintDrawnThisFrame; ///< Flag to prevent double-drawing in same frame + protected: UINT getLastDrawTime(); @@ -225,6 +232,7 @@ class WbView3d : public WbView, public DX8_CleanupHook void drawCircle( HDC hdc, const Coord3D & centerPoint, Real radius, COLORREF color ); void drawLabels(HDC hdc); void drawLabels(void); + void drawBrushModeHint(HDC hdc); void shutdownWW3D(); void killTheTimer(); void render(); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/res/WorldBuilder.rc b/GeneralsMD/Code/Tools/WorldBuilder/res/WorldBuilder.rc index ef28eed189..0a64a1773f 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/res/WorldBuilder.rc +++ b/GeneralsMD/Code/Tools/WorldBuilder/res/WorldBuilder.rc @@ -494,7 +494,7 @@ BEGIN CONTROL "",ID_SLIDER,"Static",SS_BLACKFRAME,0,1,19,62 END -IDD_BRUSH_OPTIONS DIALOG DISCARDABLE 0, 0, 118, 138 +IDD_BRUSH_OPTIONS DIALOG DISCARDABLE 0, 0, 118, 300 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION CAPTION "Height Brush Options" FONT 8, "MS Sans Serif" @@ -504,16 +504,33 @@ BEGIN EDITTEXT IDC_FEATHER_EDIT,71,55,17,12,ES_AUTOHSCROLL RTEXT "Size in cells:",IDC_STATIC,24,56,39,10 PUSHBUTTON "1",IDC_SIZE_POPUP,93,11,8,12,BS_BITMAP - GROUPBOX "Brush Width:",IDC_STATIC,1,1,113,34 - GROUPBOX "Brush Feather Width:",IDC_STATIC,1,43,113,39 + GROUPBOX "Brush Width (Shift + Scroll Wheel):",IDC_STATIC,1,1,113,34 + GROUPBOX "Brush Feather Width (Ctrl + Scroll Wheel):",IDC_STATIC,1,43,113,39 PUSHBUTTON "1",IDC_FEATHER_POPUP,93,55,8,12,BS_BITMAP LTEXT "35 FT.",IDC_WIDTH_LABEL,71,24,43,8 - LTEXT "35 FT.",IDC_HEIGHT_LABEL,71,120,36,8 - GROUPBOX "Brush Height:",IDC_STATIC,1,90,113,45 - EDITTEXT IDC_HEIGHT_EDIT,71,105,17,12,ES_AUTOHSCROLL - RTEXT "Size in 1/8 cells:",IDC_STATIC,0,107,63,10 - PUSHBUTTON "1",IDC_HEIGHT_POPUP,93,106,8,12,BS_BITMAP LTEXT "35 FT.",IDC_FEATHER_LABEL,71,68,41,8 + GROUPBOX "Raise / Lower Amount (LMB / Shift+LMB):",IDC_STATIC,1,90,113,34 + EDITTEXT IDC_RAISELOWER_EDIT,71,105,17,12,ES_AUTOHSCROLL + RTEXT "Amount:",IDC_STATIC,23,106,40,8 + PUSHBUTTON "1",IDC_RAISELOWER_POPUP,93,105,8,12,BS_BITMAP + LTEXT "0.0 FEET.",IDC_RAISELOWER_LABEL,71,118,43,8 + GROUPBOX "Set Height (Ctrl + LMB):",IDC_STATIC,1,132,113,34 + EDITTEXT IDC_HEIGHT_EDIT,71,147,17,12,ES_AUTOHSCROLL + RTEXT "Size in 1/8 cells:",IDC_STATIC,0,149,63,10 + PUSHBUTTON "1",IDC_HEIGHT_POPUP,93,148,8,12,BS_BITMAP + LTEXT "35 FT.",IDC_HEIGHT_LABEL,71,160,36,8 + GROUPBOX "Smooth (Ctrl + Shift + LMB):",IDC_STATIC,1,174,113,50 + EDITTEXT IDC_RADIUS_EDIT,71,189,17,12,ES_AUTOHSCROLL + RTEXT "Radius:",IDC_STATIC,23,190,40,8 + PUSHBUTTON "1",IDC_RADIUS_POPUP,93,189,8,12,BS_BITMAP + EDITTEXT IDC_RATE_EDIT,71,205,17,12,ES_AUTOHSCROLL + RTEXT "Rate:",IDC_STATIC,23,206,40,8 + PUSHBUTTON "1",IDC_RATE_POPUP,93,205,8,12,BS_BITMAP + GROUPBOX "Brush Modes:",IDC_STATIC,1,232,113,65 + LTEXT "LMB - Raise Height",IDC_STATIC,5,242,108,8 + LTEXT "Shift + LMB - Lower Height",IDC_STATIC,5,252,108,8 + LTEXT "Ctrl + LMB - Set Height",IDC_STATIC,5,262,108,8 + LTEXT "Ctrl + Shift + LMB - Smooth",IDC_STATIC,5,272,108,8 END IDD_TERRAIN_MATERIAL DIALOG DISCARDABLE 0, 0, 200, 286 diff --git a/GeneralsMD/Code/Tools/WorldBuilder/res/resource.h b/GeneralsMD/Code/Tools/WorldBuilder/res/resource.h index 0678ee267b..7d1deee76c 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/res/resource.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/res/resource.h @@ -202,6 +202,7 @@ #define IDC_RAISELOWER_EDIT 1800 #define IDC_RAISELOWER_POPUP 1801 #define IDC_RAISELOWER_LABEL 1802 +#define IDC_MODE_LEGEND 1803 #define IDC_TERRAIN_TREEVIEW 1033 #define IDC_SLIDER3 1034 #define IDC_SWAP_TEXTURES 1035 diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp index 2f9c4a74bc..31f64d48d0 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp @@ -55,6 +55,8 @@ #include "MainFrm.h" #include "CUndoable.h" #include "Common/Debug.h" +#include "BrushTool.h" +#include "DrawObject.h" //----------------------------------------------------------------------------- // Private Data @@ -79,7 +81,11 @@ CWorldBuilderView::CWorldBuilderView() : m_scrollMin(0,0), m_scrollMax(0,0), mShowGrid(true), - m_showTexture(true) + m_showTexture(true), + m_lastBrushMode(-1), + m_lastHintRect(0,0,0,0), + m_lastHintPos(-10000, -10000), + m_hintDrawnThisPaint(false) { Int show; show = ::AfxGetApp()->GetProfileInt(MAIN_FRAME_SECTION, "ShowContours", 0); @@ -309,6 +315,8 @@ void CWorldBuilderView::OnPaint() CPaintDC dc(this); // device context for painting + // Reset hint drawn flag for new paint cycle + m_hintDrawnThisPaint = false; // Offset the origin so that we can draw on 0 based coordinates. dc.SetViewportOrg(-mXScrollOffset, -mYScrollOffset); updateRgn.OffsetRgn(mXScrollOffset, mYScrollOffset); @@ -406,6 +414,9 @@ void CWorldBuilderView::OnPaint() } } + // Draw brush mode hint (only if in update region or needs update) + drawBrushModeHint(&dc, &updateRgn); + } @@ -1015,6 +1026,7 @@ BEGIN_MESSAGE_MAP(CWorldBuilderView, WbView) ON_WM_CREATE() ON_COMMAND(ID_VIEW_SHOWTEXTURE, OnViewShowtexture) ON_UPDATE_COMMAND_UI(ID_VIEW_SHOWTEXTURE, OnUpdateViewShowtexture) + ON_WM_MOUSEMOVE() // ON_COMMAND(ID_VIEW_SHOWCONTOURS, OnViewShowcontours) // ON_UPDATE_COMMAND_UI(ID_VIEW_SHOWCONTOURS, OnUpdateViewShowcontours) //}}AFX_MSG_MAP @@ -1081,3 +1093,156 @@ DEBUG_ASSERTCRASH((abs(curPt.y-curPt2.y)<1),("oops")); return true; } +//============================================================================= +// CWorldBuilderView::drawBrushModeHint +//============================================================================= +/** Draws the brush mode hint on canvas near the brush cursor. */ +//============================================================================= +void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) +{ + // Only show hint if brush tool is active + Tool *pCurTool = WbApp()->getCurTool(); + if (!pCurTool || dynamic_cast(pCurTool) == NULL) { + // Not brush tool - clear cache (don't invalidate during paint) + if (!m_lastHintRect.IsRectEmpty()) { + m_lastHintRect.SetRectEmpty(); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } + return; + } + + // Check if LMB is pressed - if so, hide hint + if (::GetAsyncKeyState(VK_LBUTTON) & 0x8000) { + // LMB is pressed - clear cache (don't invalidate during paint) + if (!m_lastHintRect.IsRectEmpty()) { + m_lastHintRect.SetRectEmpty(); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } + return; + } + + // Get current brush mode from modifier keys + BrushTool::EBrushMode currentMode = BrushTool::getPreviewModeFromKeys(); + Int modeInt = (Int)currentMode; + + // Get brush position from DrawObject + Coord3D brushPos = DrawObject::getFeedbackPos(); + CPoint viewPt; + if (!docToViewCoords(brushPos, &viewPt)) { + // Can't convert to view coords - clear cache (don't invalidate during paint) + if (!m_lastHintRect.IsRectEmpty()) { + m_lastHintRect.SetRectEmpty(); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } + return; + } + + // Adjust for scroll offset + viewPt.x += mXScrollOffset; + viewPt.y += mYScrollOffset; + + // Calculate hint position (offset from brush cursor) + CPoint hintPos = viewPt; + hintPos.x += 20; // Offset to the right + hintPos.y -= 20; // Offset upward + + // Get mode hint strings showing all modes with active one highlighted + char primaryBuf[256]; + char secondaryBuf[512]; + BrushTool::getModeHintStrings(primaryBuf, sizeof(primaryBuf), secondaryBuf, sizeof(secondaryBuf)); + + // Build full hint text (modes only, no scroll wheel info) + const char* firstLine = secondaryBuf && strlen(secondaryBuf) > 0 ? secondaryBuf : primaryBuf; + + // Check if we need to update the hint (mode changed or position moved significantly) + Bool needUpdate = false; + if (modeInt != m_lastBrushMode) { + needUpdate = true; + } else if (m_lastHintRect.IsRectEmpty()) { + needUpdate = true; + } else { + Int dx = abs(hintPos.x - m_lastHintPos.x); + Int dy = abs(hintPos.y - m_lastHintPos.y); + if (dx > 15 || dy > 15) { + needUpdate = true; + } + } + + if (!firstLine || strlen(firstLine) == 0) { + m_lastHintRect.SetRectEmpty(); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + return; + } + + // Calculate hint rectangle first to check if we need to draw + CRect hintRect; + CSize textSize; + + // Calculate text size (single line) + textSize = pDc->GetTextExtent(firstLine, (Int)strlen(firstLine)); + + // Create hint rectangle with padding + Int padding = 6; + hintRect.left = hintPos.x - padding; + hintRect.top = hintPos.y - textSize.cy - padding; + hintRect.right = hintPos.x + textSize.cx + padding; + hintRect.bottom = hintPos.y + padding; + + // If we don't need to update and we have a valid cached hint, use cached position + if (!needUpdate && !m_lastHintRect.IsRectEmpty()) { + // Use cached position but recalculate rect based on current position + hintPos = m_lastHintPos; + hintRect.left = hintPos.x - padding; + hintRect.top = hintPos.y - textSize.cy - padding; + hintRect.right = hintPos.x + textSize.cx + padding; + hintRect.bottom = hintPos.y + padding; + } + + // Only draw if hint is in update region or we need to update + if (pUpdateRgn && !needUpdate && !m_lastHintRect.IsRectEmpty()) { + // Check if hint rect intersects with update region + // Adjust hint rect for scroll offset to match update region coordinates + CRect adjustedHintRect = hintRect; + adjustedHintRect.OffsetRect(-mXScrollOffset, -mYScrollOffset); + if (!pUpdateRgn->RectInRegion(&adjustedHintRect)) { + // Not in update region and doesn't need update - skip drawing + return; + } + } + + // Draw text (white) - single line (no background rectangle to reduce flicker) + COLORREF oldColor = pDc->SetTextColor(RGB(255, 255, 255)); + Int oldBkMode = pDc->SetBkMode(TRANSPARENT); + + // Draw modes line + pDc->TextOut(hintPos.x, hintPos.y - textSize.cy, firstLine, (Int)strlen(firstLine)); + + pDc->SetTextColor(oldColor); + pDc->SetBkMode(oldBkMode); + + // Update cache only if we recalculated + if (needUpdate) { + m_lastHintRect = hintRect; + m_lastHintPos = hintPos; + m_lastBrushMode = modeInt; + } +} + +//============================================================================= +// CWorldBuilderView::OnMouseMove +//============================================================================= +/** Override to invalidate hint area when mouse moves. */ +//============================================================================= +void CWorldBuilderView::OnMouseMove(UINT nFlags, CPoint point) +{ + // Call base class first + WbView::OnMouseMove(nFlags, point); + + // Don't invalidate here - let OnPaint handle it naturally + // Invalidating here causes flicker due to paint feedback loops +} + diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp index 1f398f58e2..ec2797b840 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -71,6 +71,7 @@ #include "W3DDevice/Common/W3DConvert.h" #include "W3DDevice/GameClient/W3DShadow.h" #include "DrawObject.h" +#include "BrushTool.h" #include "Common/MapObject.h" #include "Common/GlobalData.h" #include "ShadowOptions.h" @@ -406,8 +407,12 @@ WbView3d::WbView3d() : m_showWeaponRanges(false), m_highlightTestArt(false), m_showLetterbox(false), - m_showSoundCircles(false) + m_showSoundCircles(false), + m_lastBrushMode(-1), + m_lastHintPos(-10000, -10000), + m_hintDrawnThisFrame(false) { + ::memset(&m_lastHintRect, 0, sizeof(m_lastHintRect)); TheTacticalView = &bogusTacticalView; m_actualWinSize.x = ::AfxGetApp()->GetProfileInt(MAIN_FRAME_SECTION, "Width", THREE_D_VIEW_WIDTH); m_actualWinSize.y = ::AfxGetApp()->GetProfileInt(MAIN_FRAME_SECTION, "Height", THREE_D_VIEW_HEIGHT); @@ -2144,6 +2149,7 @@ BEGIN_MESSAGE_MAP(WbView3d, WbView) ON_WM_CREATE() ON_WM_PAINT() ON_WM_SIZE() + ON_WM_MOUSEMOVE() ON_WM_MOUSEWHEEL() ON_WM_TIMER() ON_WM_DESTROY() @@ -2352,6 +2358,8 @@ int WbView3d::OnCreate(LPCREATESTRUCT lpCreateStruct) // ---------------------------------------------------------------------------- void WbView3d::OnPaint() { + // Reset hint drawn flag at start of paint cycle to prevent double-drawing + m_hintDrawnThisFrame = false; PAINTSTRUCT ps; HDC hdc = ::BeginPaint(m_hWnd, &ps); @@ -2641,6 +2649,9 @@ void WbView3d::drawLabels(HDC hdc) } } } + + // Draw brush mode hint (only once per frame to prevent double-drawing) + drawBrushModeHint(hdc); } @@ -3259,3 +3270,175 @@ void WbView3d::OnUpdateViewShowSoundCircles(CCmdUI* pCmdUI) pCmdUI->SetCheck(m_showSoundCircles ? 1 : 0); } +//============================================================================= +// WbView3d::OnMouseMove +//============================================================================= +/** Override to invalidate hint area when mouse moves. */ +//============================================================================= +void WbView3d::OnMouseMove(UINT nFlags, CPoint point) +{ + // Call base class first + WbView::OnMouseMove(nFlags, point); + + // Don't invalidate here - let drawLabels handle it naturally + // Invalidating here causes flicker due to paint feedback loops +} + +//============================================================================= +// WbView3d::drawBrushModeHint +//============================================================================= +/** Draws the brush mode hint on canvas near the brush cursor in 3D view. */ +//============================================================================= +void WbView3d::drawBrushModeHint(HDC hdc) +{ + // Prevent double-drawing: only draw once per frame + // Prefer D3DXFont path (hdc == NULL) over GDI path (hdc != NULL) + if (m_hintDrawnThisFrame) { + // Already drawn this frame - skip + return; + } + + // Only show hint if brush tool is active + Tool *pCurTool = WbApp()->getCurTool(); + if (!pCurTool || dynamic_cast(pCurTool) == NULL) { + // Not brush tool - clear cache (don't invalidate during paint) + if (!::IsRectEmpty(&m_lastHintRect)) { + ::SetRectEmpty(&m_lastHintRect); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } + return; + } + + // Check if LMB is pressed - if so, hide hint + if (::GetAsyncKeyState(VK_LBUTTON) & 0x8000) { + // LMB is pressed - clear cache (don't invalidate during paint) + if (!::IsRectEmpty(&m_lastHintRect)) { + ::SetRectEmpty(&m_lastHintRect); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } + return; + } + + // Get current brush mode from modifier keys + BrushTool::EBrushMode currentMode = BrushTool::getPreviewModeFromKeys(); + Int modeInt = (Int)currentMode; + + // Get brush position from DrawObject + Coord3D brushPos = DrawObject::getFeedbackPos(); + + // Project world position to screen + Vector3 worldPos, screenPos; + worldPos.Set(brushPos.x, brushPos.y, brushPos.z); + if (CameraClass::INSIDE_FRUSTUM != m_camera->Project(screenPos, worldPos)) { + // Not visible - clear cache (don't invalidate during paint) + if (!::IsRectEmpty(&m_lastHintRect)) { + ::SetRectEmpty(&m_lastHintRect); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } + return; + } + + // Convert to pixel coordinates + CRect rClient; + GetClientRect(&rClient); + Int sx, sy; + W3DLogicalScreenToPixelScreen(screenPos.X, screenPos.Y, &sx, &sy, + rClient.right - rClient.left, rClient.bottom - rClient.top); + CPoint hintPos; + hintPos.x = rClient.left + sx + 20; // Offset to the right + hintPos.y = rClient.top + sy - 20; // Offset upward + + // Get mode hint strings showing all modes with active one highlighted + char primaryBuf[256]; + char secondaryBuf[512]; + BrushTool::getModeHintStrings(primaryBuf, sizeof(primaryBuf), secondaryBuf, sizeof(secondaryBuf)); + + // Build full hint text (modes only, no scroll wheel info) + const char* firstLine = secondaryBuf && strlen(secondaryBuf) > 0 ? secondaryBuf : primaryBuf; + + // Check if we need to update the hint (mode changed or position moved significantly) + Bool needUpdate = false; + if (modeInt != m_lastBrushMode) { + needUpdate = true; + } else if (::IsRectEmpty(&m_lastHintRect)) { + needUpdate = true; + } else { + Int dx = abs(hintPos.x - m_lastHintPos.x); + Int dy = abs(hintPos.y - m_lastHintPos.y); + if (dx > 15 || dy > 15) { + needUpdate = true; + } + } + + if (!firstLine || strlen(firstLine) == 0) { + ::SetRectEmpty(&m_lastHintRect); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + return; + } + + // If we don't need to update and we have a valid cached hint, use cached values + RECT hintRect; + SIZE textSize; + if (!needUpdate && !::IsRectEmpty(&m_lastHintRect)) { + // Use cached position and rect + hintPos = m_lastHintPos; + hintRect = m_lastHintRect; + // Still need to calculate text size for drawing + if (m3DFont && !hdc) { + textSize.cx = (Int)strlen(firstLine) * 6; + textSize.cy = 16; + } else { + ::GetTextExtentPoint32(hdc, firstLine, (Int)strlen(firstLine), &textSize); + } + } else { + // Calculate text size (single line) + if (m3DFont && !hdc) { + // Use D3DXFont - estimate size + textSize.cx = (Int)strlen(firstLine) * 6; + textSize.cy = 16; + } else { + // Use GDI + ::GetTextExtentPoint32(hdc, firstLine, (Int)strlen(firstLine), &textSize); + } + + // Create hint rectangle with padding + Int padding = 6; + hintRect.left = hintPos.x - padding; + hintRect.top = hintPos.y - textSize.cy - padding; + hintRect.right = hintPos.x + textSize.cx + padding; + hintRect.bottom = hintPos.y + padding; + } + + // Draw text only (no background rectangle to reduce flicker) + if (m3DFont && !hdc) { + // Use D3DXFont + // Draw modes line + RECT textRect = hintRect; + textRect.bottom = textRect.top + textSize.cy; + m3DFont->DrawText(firstLine, (Int)strlen(firstLine), &textRect, + DT_LEFT | DT_NOCLIP | DT_TOP | DT_SINGLELINE, 0xFFFFFFFF); + } else if (hdc) { + // Use GDI + // Draw text (white) - single line (no background) + ::SetBkMode(hdc, TRANSPARENT); + ::SetTextColor(hdc, RGB(255, 255, 255)); + + // Draw modes line + ::TextOut(hdc, hintPos.x, hintPos.y - textSize.cy, firstLine, (Int)strlen(firstLine)); + } + + // Update cache only if we recalculated + if (needUpdate) { + m_lastHintRect = hintRect; + m_lastHintPos = hintPos; + m_lastBrushMode = modeInt; + } + + // Mark as drawn this frame + m_hintDrawnThisFrame = true; +} + From 14299cccc2ccdb39ecde63d872cb09e8a1711166 Mon Sep 17 00:00:00 2001 From: "DESKTOP-ADL4CP8\\user" Date: Wed, 26 Nov 2025 03:43:35 +0200 Subject: [PATCH 4/7] fix(worldbuilder): adress review comments --- .../Tools/WorldBuilder/include/BrushTool.h | 6 +-- .../Tools/WorldBuilder/include/brushoptions.h | 4 +- .../WorldBuilder/include/WorldBuilderView.h | 8 +-- .../Tools/WorldBuilder/include/brushoptions.h | 13 ++--- .../Code/Tools/WorldBuilder/src/BrushTool.cpp | 10 ++-- .../Tools/WorldBuilder/src/WorldBuilder.cpp | 4 -- .../WorldBuilder/src/WorldBuilderView.cpp | 44 +++-------------- .../Code/Tools/WorldBuilder/src/wbview.cpp | 19 ++----- .../Code/Tools/WorldBuilder/src/wbview3d.cpp | 49 ++++--------------- 9 files changed, 41 insertions(+), 116 deletions(-) diff --git a/Generals/Code/Tools/WorldBuilder/include/BrushTool.h b/Generals/Code/Tools/WorldBuilder/include/BrushTool.h index 19c829ca14..979baa4a7b 100644 --- a/Generals/Code/Tools/WorldBuilder/include/BrushTool.h +++ b/Generals/Code/Tools/WorldBuilder/include/BrushTool.h @@ -45,9 +45,9 @@ class BrushTool : public Tool ~BrushTool(void); public: - static Int getWidth(void) {return m_brushWidth;}; ///GetHeightMap()->duplicate(); REF_PTR_RELEASE(m_htMapFeatherCopy); - m_htMapFeatherCopy = m_htMapEditCopy->duplicate(); REF_PTR_RELEASE(m_htMapRateCopy); + m_htMapEditCopy = pDoc->GetHeightMap()->duplicate(); + m_htMapFeatherCopy = m_htMapEditCopy->duplicate(); + if (m_activeMode == BRUSH_MODE_SMOOTH) { m_htMapRateCopy = m_htMapEditCopy->duplicate(); resetSmoothRateBuffer(); @@ -184,7 +182,7 @@ void BrushTool::mouseUp(TTrackingMode m, CPoint viewPt, WbView* pView, CWorldBui WBDocUndoable *pUndo = new WBDocUndoable(pDoc, m_htMapEditCopy); pDoc->AddAndDoUndoable(pUndo); - REF_PTR_RELEASE(pUndo); // belongs to pDoc now. + REF_PTR_RELEASE(pUndo); REF_PTR_RELEASE(m_htMapEditCopy); REF_PTR_RELEASE(m_htMapFeatherCopy); REF_PTR_RELEASE(m_htMapRateCopy); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp index 81ebb93205..e09a7552f5 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp @@ -566,16 +566,12 @@ void CWorldBuilderApp::updateCurTool(Bool forceHand) DEBUG_ASSERTCRASH((m_lockCurTool>=0),("oops")); if (!m_lockCurTool) { // don't change tools that are doing something. if (forceHand || (0x8000 & ::GetAsyncKeyState(VK_SPACE))) { - // Space bar gives scroll hand. m_curTool = &m_handScrollTool; } else if (0x8000 & ::GetAsyncKeyState(VK_MENU)) { - // Alt key gives eyedropper. m_curTool = &m_eyedropperTool; } else if (0x8000 & ::GetAsyncKeyState('W')) { - // W key gives pointer. m_curTool = &m_pointerTool; } else { - // Else the tool selected in the tool palette. m_curTool = m_selTool; } } diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp index 31f64d48d0..b36295228d 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp @@ -315,7 +315,6 @@ void CWorldBuilderView::OnPaint() CPaintDC dc(this); // device context for painting - // Reset hint drawn flag for new paint cycle m_hintDrawnThisPaint = false; // Offset the origin so that we can draw on 0 based coordinates. dc.SetViewportOrg(-mXScrollOffset, -mYScrollOffset); @@ -416,7 +415,6 @@ void CWorldBuilderView::OnPaint() // Draw brush mode hint (only if in update region or needs update) drawBrushModeHint(&dc, &updateRgn); - } @@ -1100,10 +1098,8 @@ DEBUG_ASSERTCRASH((abs(curPt.y-curPt2.y)<1),("oops")); //============================================================================= void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) { - // Only show hint if brush tool is active Tool *pCurTool = WbApp()->getCurTool(); - if (!pCurTool || dynamic_cast(pCurTool) == NULL) { - // Not brush tool - clear cache (don't invalidate during paint) + if (!pCurTool || pCurTool->getToolID() != ID_BRUSH_TOOL) { if (!m_lastHintRect.IsRectEmpty()) { m_lastHintRect.SetRectEmpty(); m_lastBrushMode = -1; @@ -1112,9 +1108,7 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) return; } - // Check if LMB is pressed - if so, hide hint if (::GetAsyncKeyState(VK_LBUTTON) & 0x8000) { - // LMB is pressed - clear cache (don't invalidate during paint) if (!m_lastHintRect.IsRectEmpty()) { m_lastHintRect.SetRectEmpty(); m_lastBrushMode = -1; @@ -1123,15 +1117,12 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) return; } - // Get current brush mode from modifier keys BrushTool::EBrushMode currentMode = BrushTool::getPreviewModeFromKeys(); Int modeInt = (Int)currentMode; - // Get brush position from DrawObject Coord3D brushPos = DrawObject::getFeedbackPos(); CPoint viewPt; if (!docToViewCoords(brushPos, &viewPt)) { - // Can't convert to view coords - clear cache (don't invalidate during paint) if (!m_lastHintRect.IsRectEmpty()) { m_lastHintRect.SetRectEmpty(); m_lastBrushMode = -1; @@ -1140,24 +1131,19 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) return; } - // Adjust for scroll offset viewPt.x += mXScrollOffset; viewPt.y += mYScrollOffset; - // Calculate hint position (offset from brush cursor) CPoint hintPos = viewPt; - hintPos.x += 20; // Offset to the right - hintPos.y -= 20; // Offset upward + hintPos.x += 20; + hintPos.y -= 20; - // Get mode hint strings showing all modes with active one highlighted char primaryBuf[256]; char secondaryBuf[512]; BrushTool::getModeHintStrings(primaryBuf, sizeof(primaryBuf), secondaryBuf, sizeof(secondaryBuf)); - // Build full hint text (modes only, no scroll wheel info) const char* firstLine = secondaryBuf && strlen(secondaryBuf) > 0 ? secondaryBuf : primaryBuf; - // Check if we need to update the hint (mode changed or position moved significantly) Bool needUpdate = false; if (modeInt != m_lastBrushMode) { needUpdate = true; @@ -1172,29 +1158,26 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) } if (!firstLine || strlen(firstLine) == 0) { - m_lastHintRect.SetRectEmpty(); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); + if (!m_lastHintRect.IsRectEmpty()) { + m_lastHintRect.SetRectEmpty(); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } return; } - // Calculate hint rectangle first to check if we need to draw CRect hintRect; CSize textSize; - // Calculate text size (single line) textSize = pDc->GetTextExtent(firstLine, (Int)strlen(firstLine)); - // Create hint rectangle with padding Int padding = 6; hintRect.left = hintPos.x - padding; hintRect.top = hintPos.y - textSize.cy - padding; hintRect.right = hintPos.x + textSize.cx + padding; hintRect.bottom = hintPos.y + padding; - // If we don't need to update and we have a valid cached hint, use cached position if (!needUpdate && !m_lastHintRect.IsRectEmpty()) { - // Use cached position but recalculate rect based on current position hintPos = m_lastHintPos; hintRect.left = hintPos.x - padding; hintRect.top = hintPos.y - textSize.cy - padding; @@ -1202,29 +1185,22 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) hintRect.bottom = hintPos.y + padding; } - // Only draw if hint is in update region or we need to update if (pUpdateRgn && !needUpdate && !m_lastHintRect.IsRectEmpty()) { - // Check if hint rect intersects with update region - // Adjust hint rect for scroll offset to match update region coordinates CRect adjustedHintRect = hintRect; adjustedHintRect.OffsetRect(-mXScrollOffset, -mYScrollOffset); if (!pUpdateRgn->RectInRegion(&adjustedHintRect)) { - // Not in update region and doesn't need update - skip drawing return; } } - // Draw text (white) - single line (no background rectangle to reduce flicker) COLORREF oldColor = pDc->SetTextColor(RGB(255, 255, 255)); Int oldBkMode = pDc->SetBkMode(TRANSPARENT); - // Draw modes line pDc->TextOut(hintPos.x, hintPos.y - textSize.cy, firstLine, (Int)strlen(firstLine)); pDc->SetTextColor(oldColor); pDc->SetBkMode(oldBkMode); - // Update cache only if we recalculated if (needUpdate) { m_lastHintRect = hintRect; m_lastHintPos = hintPos; @@ -1239,10 +1215,6 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) //============================================================================= void CWorldBuilderView::OnMouseMove(UINT nFlags, CPoint point) { - // Call base class first WbView::OnMouseMove(nFlags, point); - - // Don't invalidate here - let OnPaint handle it naturally - // Invalidating here causes flicker due to paint feedback loops } diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview.cpp index 7529ffdedb..b0807b4e52 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview.cpp @@ -1089,51 +1089,38 @@ void WbView::rulerFeedbackInfo(Coord3D &point1, Coord3D &point2, Real dist) // ---------------------------------------------------------------------------- BOOL WbView::OnMouseWheel(UINT nFlags, short zDelta, CPoint point) { - // Check if brush tool should handle this if (handleBrushMouseWheel(nFlags, zDelta)) { return TRUE; } - // Default behavior: pass to base class for scrolling return CView::OnMouseWheel(nFlags, zDelta, point); } // ---------------------------------------------------------------------------- Bool WbView::handleBrushMouseWheel(UINT nFlags, short zDelta) { - // Only handle if brush tool is active Tool *pCurTool = WbApp()->getCurTool(); - if (pCurTool && dynamic_cast(pCurTool) != NULL) { + if (pCurTool && pCurTool->getToolID() == ID_BRUSH_TOOL) { Bool shiftDown = (nFlags & MK_SHIFT) != 0; Bool ctrlDown = (nFlags & MK_CONTROL) != 0; if (shiftDown && !ctrlDown) { - // Shift + Scroll Wheel -> Adjust brush width Int currentWidth = BrushTool::getWidth(); Int delta = zDelta > 0 ? 1 : -1; - Int newWidth = currentWidth + delta; - // Clamp to reasonable range (1-100) - if (newWidth < 1) newWidth = 1; - if (newWidth > 100) newWidth = 100; + Int newWidth = max(1, min(100, currentWidth + delta)); BrushTool::setWidth(newWidth); - // Update status bar CString statusText; statusText.Format("Brush Width: %d", newWidth); CMainFrame::GetMainFrame()->SetMessageText(statusText); return TRUE; } else if (ctrlDown && !shiftDown) { - // Control + Scroll Wheel -> Adjust brush feather Int currentFeather = BrushTool::getFeather(); Int delta = zDelta > 0 ? 1 : -1; - Int newFeather = currentFeather + delta; - // Clamp to reasonable range (0-100) - if (newFeather < 0) newFeather = 0; - if (newFeather > 100) newFeather = 100; + Int newFeather = max(0, min(100, currentFeather + delta)); BrushTool::setFeather(newFeather); - // Update status bar CString statusText; statusText.Format("Brush Feather: %d", newFeather); CMainFrame::GetMainFrame()->SetMessageText(statusText); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp index ec2797b840..f982f2169d 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -407,12 +407,12 @@ WbView3d::WbView3d() : m_showWeaponRanges(false), m_highlightTestArt(false), m_showLetterbox(false), - m_showSoundCircles(false), + m_showSoundCircles(false), m_lastBrushMode(-1), m_lastHintPos(-10000, -10000), m_hintDrawnThisFrame(false) { - ::memset(&m_lastHintRect, 0, sizeof(m_lastHintRect)); + ::SetRectEmpty(&m_lastHintRect); TheTacticalView = &bogusTacticalView; m_actualWinSize.x = ::AfxGetApp()->GetProfileInt(MAIN_FRAME_SECTION, "Width", THREE_D_VIEW_WIDTH); m_actualWinSize.y = ::AfxGetApp()->GetProfileInt(MAIN_FRAME_SECTION, "Height", THREE_D_VIEW_HEIGHT); @@ -3291,17 +3291,12 @@ void WbView3d::OnMouseMove(UINT nFlags, CPoint point) //============================================================================= void WbView3d::drawBrushModeHint(HDC hdc) { - // Prevent double-drawing: only draw once per frame - // Prefer D3DXFont path (hdc == NULL) over GDI path (hdc != NULL) if (m_hintDrawnThisFrame) { - // Already drawn this frame - skip return; } - // Only show hint if brush tool is active Tool *pCurTool = WbApp()->getCurTool(); - if (!pCurTool || dynamic_cast(pCurTool) == NULL) { - // Not brush tool - clear cache (don't invalidate during paint) + if (!pCurTool || pCurTool->getToolID() != ID_BRUSH_TOOL) { if (!::IsRectEmpty(&m_lastHintRect)) { ::SetRectEmpty(&m_lastHintRect); m_lastBrushMode = -1; @@ -3310,9 +3305,7 @@ void WbView3d::drawBrushModeHint(HDC hdc) return; } - // Check if LMB is pressed - if so, hide hint if (::GetAsyncKeyState(VK_LBUTTON) & 0x8000) { - // LMB is pressed - clear cache (don't invalidate during paint) if (!::IsRectEmpty(&m_lastHintRect)) { ::SetRectEmpty(&m_lastHintRect); m_lastBrushMode = -1; @@ -3321,18 +3314,14 @@ void WbView3d::drawBrushModeHint(HDC hdc) return; } - // Get current brush mode from modifier keys BrushTool::EBrushMode currentMode = BrushTool::getPreviewModeFromKeys(); Int modeInt = (Int)currentMode; - // Get brush position from DrawObject Coord3D brushPos = DrawObject::getFeedbackPos(); - // Project world position to screen Vector3 worldPos, screenPos; worldPos.Set(brushPos.x, brushPos.y, brushPos.z); if (CameraClass::INSIDE_FRUSTUM != m_camera->Project(screenPos, worldPos)) { - // Not visible - clear cache (don't invalidate during paint) if (!::IsRectEmpty(&m_lastHintRect)) { ::SetRectEmpty(&m_lastHintRect); m_lastBrushMode = -1; @@ -3341,25 +3330,21 @@ void WbView3d::drawBrushModeHint(HDC hdc) return; } - // Convert to pixel coordinates CRect rClient; GetClientRect(&rClient); Int sx, sy; W3DLogicalScreenToPixelScreen(screenPos.X, screenPos.Y, &sx, &sy, rClient.right - rClient.left, rClient.bottom - rClient.top); CPoint hintPos; - hintPos.x = rClient.left + sx + 20; // Offset to the right - hintPos.y = rClient.top + sy - 20; // Offset upward + hintPos.x = rClient.left + sx + 20; + hintPos.y = rClient.top + sy - 20; - // Get mode hint strings showing all modes with active one highlighted char primaryBuf[256]; char secondaryBuf[512]; BrushTool::getModeHintStrings(primaryBuf, sizeof(primaryBuf), secondaryBuf, sizeof(secondaryBuf)); - // Build full hint text (modes only, no scroll wheel info) const char* firstLine = secondaryBuf && strlen(secondaryBuf) > 0 ? secondaryBuf : primaryBuf; - // Check if we need to update the hint (mode changed or position moved significantly) Bool needUpdate = false; if (modeInt != m_lastBrushMode) { needUpdate = true; @@ -3374,20 +3359,19 @@ void WbView3d::drawBrushModeHint(HDC hdc) } if (!firstLine || strlen(firstLine) == 0) { - ::SetRectEmpty(&m_lastHintRect); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); + if (!::IsRectEmpty(&m_lastHintRect)) { + ::SetRectEmpty(&m_lastHintRect); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } return; } - // If we don't need to update and we have a valid cached hint, use cached values RECT hintRect; SIZE textSize; if (!needUpdate && !::IsRectEmpty(&m_lastHintRect)) { - // Use cached position and rect hintPos = m_lastHintPos; hintRect = m_lastHintRect; - // Still need to calculate text size for drawing if (m3DFont && !hdc) { textSize.cx = (Int)strlen(firstLine) * 6; textSize.cy = 16; @@ -3395,17 +3379,13 @@ void WbView3d::drawBrushModeHint(HDC hdc) ::GetTextExtentPoint32(hdc, firstLine, (Int)strlen(firstLine), &textSize); } } else { - // Calculate text size (single line) if (m3DFont && !hdc) { - // Use D3DXFont - estimate size textSize.cx = (Int)strlen(firstLine) * 6; textSize.cy = 16; } else { - // Use GDI ::GetTextExtentPoint32(hdc, firstLine, (Int)strlen(firstLine), &textSize); } - // Create hint rectangle with padding Int padding = 6; hintRect.left = hintPos.x - padding; hintRect.top = hintPos.y - textSize.cy - padding; @@ -3413,32 +3393,23 @@ void WbView3d::drawBrushModeHint(HDC hdc) hintRect.bottom = hintPos.y + padding; } - // Draw text only (no background rectangle to reduce flicker) if (m3DFont && !hdc) { - // Use D3DXFont - // Draw modes line RECT textRect = hintRect; textRect.bottom = textRect.top + textSize.cy; m3DFont->DrawText(firstLine, (Int)strlen(firstLine), &textRect, DT_LEFT | DT_NOCLIP | DT_TOP | DT_SINGLELINE, 0xFFFFFFFF); } else if (hdc) { - // Use GDI - // Draw text (white) - single line (no background) ::SetBkMode(hdc, TRANSPARENT); ::SetTextColor(hdc, RGB(255, 255, 255)); - // Draw modes line ::TextOut(hdc, hintPos.x, hintPos.y - textSize.cy, firstLine, (Int)strlen(firstLine)); } - // Update cache only if we recalculated if (needUpdate) { m_lastHintRect = hintRect; m_lastHintPos = hintPos; m_lastBrushMode = modeInt; } - // Mark as drawn this frame m_hintDrawnThisFrame = true; } - From f5cae0fcaa914845efbceec5f0e44f3dd12fd8a7 Mon Sep 17 00:00:00 2001 From: Dimitris Kalyvas Date: Sun, 30 Nov 2025 01:16:34 +0200 Subject: [PATCH 5/7] fix/refactor(worldbuilder): address newer review comments - Fix small buffer size for FEET format string - Cache strlen result, consolidate repeated code blocks - Extract clearBrushModeHintState() helpers to reduce duplication - Restore EA comments, fix indentation, clean up misleading docstrings --- .../Tools/WorldBuilder/include/BrushTool.h | 21 +++ .../WorldBuilder/include/WorldBuilderView.h | 8 +- .../Tools/WorldBuilder/include/wbview3d.h | 3 +- .../Code/Tools/WorldBuilder/src/BrushTool.cpp | 165 +++++++++++++----- .../Tools/WorldBuilder/src/FeatherTool.cpp | 51 ++---- .../WorldBuilder/src/WorldBuilderView.cpp | 111 +++++------- .../Tools/WorldBuilder/src/brushoptions.cpp | 2 +- .../Code/Tools/WorldBuilder/src/wbview3d.cpp | 121 ++++++------- 8 files changed, 258 insertions(+), 224 deletions(-) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h b/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h index e3e5df67ff..69c91f0e88 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h @@ -87,6 +87,27 @@ class BrushTool : public Tool static EBrushMode getPreviewModeFromKeys(); static const char* getModeDisplayName(EBrushMode mode); static void getModeHintStrings(char *primaryBuf, Int primaryBufSize, char *secondaryBuf, Int secondaryBufSize); + + // Helper functions for brush hint drawing (shared between 2D and 3D views) + struct BrushHintInfo { + Bool shouldShow; + Bool shouldClear; + EBrushMode currentMode; + Int modeInt; + CPoint hintPos; + }; + static Bool getBrushHintInfo(BrushHintInfo &info, char *hintTextBuf, Int hintTextBufSize, const CPoint &hintPos, Int lastBrushMode); + + // Shared smoothing algorithm function (processes a single cell) + static void applySmoothingAlgorithm( + WorldHeightMapEdit *editMap, + WorldHeightMapEdit *featherMap, + Int i, Int j, + Int rate, + Int smoothRadius, + Int minRadius, + Int maxRadius, + CWorldBuilderDoc *pDoc); public: virtual void mouseDown(TTrackingMode m, CPoint viewPt, WbView* pView, CWorldBuilderDoc *pDoc); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h b/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h index 5e504714e7..ba6e91b1e3 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h @@ -74,10 +74,10 @@ class CWorldBuilderView : public WbView CPoint m_scrollMax; ///< The maximum scroll bar positions. // Brush mode hint flicker reduction - Int m_lastBrushMode; ///< Last brush mode drawn (for flicker reduction) - CRect m_lastHintRect; ///< Last hint rectangle drawn (for flicker reduction) + Int m_lastBrushMode; ///< Last brush mode drawn (for flicker reduction) + CRect m_lastHintRect; ///< Last hint rectangle drawn (for flicker reduction) CPoint m_lastHintPos; ///< Last hint position (for flicker reduction) - Bool m_hintDrawnThisPaint; ///< Flag to prevent double-drawing in same paint cycle + Bool m_hintDrawnThisPaint; ///< Flag to prevent double-drawing in same paint cycle protected: @@ -106,6 +106,8 @@ class CWorldBuilderView : public WbView /// Draw brush mode hint on canvas void drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn = NULL); + /// Clears the brush mode hint state + void clearBrushModeHintState(); public: /// Get the current draw size in pixels in the 2d window of one height map cell. diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h index 7dbfbc1a75..daff7b754d 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h @@ -220,7 +220,7 @@ class WbView3d : public WbView, public DX8_CleanupHook // Brush mode hint flicker reduction Int m_lastBrushMode; ///< Last brush mode drawn (for flicker reduction) RECT m_lastHintRect; ///< Last hint rectangle drawn (for flicker reduction) - CPoint m_lastHintPos; ///< Last hint position (for flicker reduction) + CPoint m_lastHintPos; ///< Last hint position (for flicker reduction) Bool m_hintDrawnThisFrame; ///< Flag to prevent double-drawing in same frame protected: @@ -233,6 +233,7 @@ class WbView3d : public WbView, public DX8_CleanupHook void drawLabels(HDC hdc); void drawLabels(void); void drawBrushModeHint(HDC hdc); + void clearBrushModeHintState(); void shutdownWW3D(); void killTheTimer(); void render(); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/BrushTool.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/BrushTool.cpp index 6a4788d885..ac01f0e5ab 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/BrushTool.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/BrushTool.cpp @@ -31,6 +31,7 @@ #include "WorldBuilderView.h" #include "brushoptions.h" #include "DrawObject.h" +#include "WorldBuilder.h" // // BrushTool class. // @@ -79,6 +80,7 @@ void BrushTool::setHeight(Int height) { if (m_brushHeight != height) { m_brushHeight = height; + // notify height palette options panel BrushOptions::setHeight(height); } }; @@ -118,6 +120,7 @@ void BrushTool::setWidth(Int width) { if (m_brushWidth != width) { m_brushWidth = width; + // notify brush palette options panel BrushOptions::setWidth(width); DrawObject::setBrushFeedbackParms(m_brushSquare, m_brushWidth, m_brushFeather); } @@ -128,6 +131,7 @@ void BrushTool::setFeather(Int feather) { if (m_brushFeather != feather) { m_brushFeather = feather; + // notify height palette options panel BrushOptions::setFeather(feather); DrawObject::setBrushFeedbackParms(m_brushSquare, m_brushWidth, m_brushFeather); } @@ -150,6 +154,7 @@ void BrushTool::mouseDown(TTrackingMode m, CPoint viewPt, WbView* pView, CWorldB m_activeMode = determineBrushMode(); + // just in case, release it. REF_PTR_RELEASE(m_htMapEditCopy); REF_PTR_RELEASE(m_htMapFeatherCopy); REF_PTR_RELEASE(m_htMapRateCopy); @@ -182,7 +187,7 @@ void BrushTool::mouseUp(TTrackingMode m, CPoint viewPt, WbView* pView, CWorldBui WBDocUndoable *pUndo = new WBDocUndoable(pDoc, m_htMapEditCopy); pDoc->AddAndDoUndoable(pUndo); - REF_PTR_RELEASE(pUndo); + REF_PTR_RELEASE(pUndo); // belongs to pDoc now. REF_PTR_RELEASE(m_htMapEditCopy); REF_PTR_RELEASE(m_htMapFeatherCopy); REF_PTR_RELEASE(m_htMapRateCopy); @@ -378,46 +383,15 @@ void BrushTool::applySmoothBrush(const CPoint &ndx, Int brushWidth, CWorldBuilde } m_htMapRateCopy->setHeight(i,j,rate); - Int total=0; - Real numSamples=0; - Int ii, jj; - Int radius = m_smoothRadius; - if (radiusMAX_SMOOTH_RADIUS) radius = MAX_SMOOTH_RADIUS; - for (ii = i-radius; ii < i+radius+1; ii++) { - for (jj = j-radius; jjradius) { - factor = 0; - } else { - factor = 1.0f - (dist-1)/radius; - } - } - int iNdx = ii; - if (iNdx<0) iNdx = 1; - if (iNdx >=m_htMapEditCopy->getXExtent()) { - iNdx = m_htMapEditCopy->getXExtent()-1; - } - int jNdx = jj; - if (jNdx<0) jNdx = 1; - if (jNdx >=m_htMapEditCopy->getYExtent()) { - jNdx = m_htMapEditCopy->getYExtent()-1; - } - total += m_htMapFeatherCopy->getHeight(iNdx, jNdx); - numSamples+=1; - } - } - total = floor((total/numSamples)); - UnsignedByte origHeight = m_htMapFeatherCopy->getHeight(i, j); - float rateF = rate/255.0f; - total = floor(origHeight*(1.0f-rateF) + total*rateF + 0.5f); - m_htMapEditCopy->setHeight(i, j, total); - pDoc->invalCell(i, j); + applySmoothingAlgorithm( + m_htMapEditCopy, + m_htMapFeatherCopy, + i, j, + rate, + m_smoothRadius, + MIN_SMOOTH_RADIUS, + MAX_SMOOTH_RADIUS, + pDoc); } } } @@ -515,6 +489,113 @@ void BrushTool::getModeHintStrings(char *primaryBuf, Int primaryBufSize, char *s defaultPrefix, defaultSuffix, shiftPrefix, shiftSuffix, ctrlPrefix, ctrlSuffix, - ctrlShiftPrefix, ctrlShiftSuffix); + ctrlShiftPrefix, ctrlShiftSuffix); + } +} + +Bool BrushTool::getBrushHintInfo(BrushHintInfo &info, char *hintTextBuf, Int hintTextBufSize, const CPoint &hintPos, Int lastBrushMode) +{ + info.shouldShow = false; + info.shouldClear = false; + info.hintPos = hintPos; + + if (!hintTextBuf || hintTextBufSize <= 0) { + return false; + } + hintTextBuf[0] = '\0'; + + Tool *pCurTool = WbApp()->getCurTool(); + if (!pCurTool || pCurTool->getToolID() != ID_BRUSH_TOOL) { + info.shouldClear = true; + return false; + } + + if (::GetAsyncKeyState(VK_LBUTTON) & 0x8000) { + info.shouldClear = true; + return false; + } + + info.currentMode = getPreviewModeFromKeys(); + info.modeInt = (Int)info.currentMode; + + char primaryBuf[256]; + char secondaryBuf[512]; + getModeHintStrings(primaryBuf, sizeof(primaryBuf), secondaryBuf, sizeof(secondaryBuf)); + + // Prefer secondary buffer (full hint) if available, otherwise use primary (current mode) + const char* hintText = NULL; + if (secondaryBuf && strlen(secondaryBuf) > 0) { + hintText = secondaryBuf; + } else if (primaryBuf && strlen(primaryBuf) > 0) { + hintText = primaryBuf; + } + + if (!hintText || strlen(hintText) == 0) { + info.shouldClear = true; + return false; + } + + strlcpy(hintTextBuf, hintText, hintTextBufSize); + info.shouldShow = true; + return true; +} + +void BrushTool::applySmoothingAlgorithm( + WorldHeightMapEdit *editMap, + WorldHeightMapEdit *featherMap, + Int i, Int j, + Int rate, + Int smoothRadius, + Int minRadius, + Int maxRadius, + CWorldBuilderDoc *pDoc) +{ + if (i < 0 || i >= editMap->getXExtent() || j < 0 || j >= editMap->getYExtent()) { + return; + } + + Int radius = smoothRadius; + if (radius < minRadius) radius = minRadius; + if (radius > maxRadius) radius = maxRadius; + + Int total = 0; + Real numSamples = 0; + + for (Int ii = i - radius; ii < i + radius + 1; ii++) { + for (Int jj = j - radius; jj < j + radius + 1; jj++) { + Real factor; + if (i == ii && j == jj) { + factor = 1.0f; + } else { + Real dist = sqrt((Real)((ii - i) * (ii - i) + (jj - j) * (jj - j))); + if (dist < 1.0f) dist = 1.0f; + if (dist > radius) { + factor = 0; + } else { + factor = 1.0f - (dist - 1) / radius; + } + } + + int iNdx = ii; + if (iNdx < 0) iNdx = 1; + if (iNdx >= editMap->getXExtent()) { + iNdx = editMap->getXExtent() - 1; + } + int jNdx = jj; + if (jNdx < 0) jNdx = 1; + if (jNdx >= editMap->getYExtent()) { + jNdx = editMap->getYExtent() - 1; + } + + total += featherMap->getHeight(iNdx, jNdx); + numSamples += 1; + } } + + total = (Int)floor(total / numSamples); + UnsignedByte origHeight = featherMap->getHeight(i, j); + float rateF = rate / 255.0f; + total = (Int)floor(origHeight * (1.0f - rateF) + total * rateF + 0.5f); + editMap->setHeight(i, j, total); + pDoc->invalCell(i, j); } diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp index b50fbd0d40..c4d9c0ddd5 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp @@ -31,6 +31,7 @@ #include "WHeightMapEdit.h" #include "WorldBuilderDoc.h" #include "WorldBuilderView.h" +#include "BrushTool.h" // // FeatherTool class. Int FeatherTool::m_feather = 0; @@ -182,46 +183,16 @@ void FeatherTool::mouseMoved(TTrackingMode m, CPoint viewPt, WbView* pView, CWor redoRate = true; } m_htMapRateCopy->setHeight(i,j,rate); - Int total=0; - Real numSamples=0; - Int ii, jj; - Int radius = m_radius; - if (radius<1) radius=1; - if (radius>FeatherOptions::MAX_RADIUS) radius = FeatherOptions::MAX_RADIUS; - for (ii = i-radius; ii < i+radius+1; ii++) { - for (jj = j-radius; jjradius) { - factor = 0; - } else { - factor = 1.0f - (dist-1)/radius; - } - } - int iNdx = ii; - if (iNdx<0) iNdx = 1; - if (iNdx >=m_htMapEditCopy->getXExtent()) { - iNdx = m_htMapEditCopy->getXExtent()-1; - } - int jNdx = jj; - if (jNdx<0) jNdx = 1; - if (jNdx >=m_htMapEditCopy->getYExtent()) { - jNdx = m_htMapEditCopy->getYExtent()-1; - } - total += m_htMapFeatherCopy->getHeight(iNdx, jNdx); - numSamples+=1; - } - } - total = floor((total/numSamples)); - UnsignedByte origHeight = m_htMapFeatherCopy->getHeight(i, j); - float rateF = rate/255.0; - total = floor(origHeight*(1.0f-rateF) + total*rateF + 0.5f); - m_htMapEditCopy->setHeight(i, j, total); - pDoc->invalCell(i, j); + + BrushTool::applySmoothingAlgorithm( + m_htMapEditCopy, + m_htMapFeatherCopy, + i, j, + rate, + m_radius, + 1, + FeatherOptions::MAX_RADIUS, + pDoc); } } } diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp index b36295228d..a61b9dfac4 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp @@ -1091,6 +1091,20 @@ DEBUG_ASSERTCRASH((abs(curPt.y-curPt2.y)<1),("oops")); return true; } +//============================================================================= +// CWorldBuilderView::clearBrushModeHintState +//============================================================================= +/** Clears the brush mode hint state. */ +//============================================================================= +void CWorldBuilderView::clearBrushModeHintState() +{ + if (!m_lastHintRect.IsRectEmpty()) { + m_lastHintRect.SetRectEmpty(); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } +} + //============================================================================= // CWorldBuilderView::drawBrushModeHint //============================================================================= @@ -1098,36 +1112,10 @@ DEBUG_ASSERTCRASH((abs(curPt.y-curPt2.y)<1),("oops")); //============================================================================= void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) { - Tool *pCurTool = WbApp()->getCurTool(); - if (!pCurTool || pCurTool->getToolID() != ID_BRUSH_TOOL) { - if (!m_lastHintRect.IsRectEmpty()) { - m_lastHintRect.SetRectEmpty(); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } - return; - } - - if (::GetAsyncKeyState(VK_LBUTTON) & 0x8000) { - if (!m_lastHintRect.IsRectEmpty()) { - m_lastHintRect.SetRectEmpty(); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } - return; - } - - BrushTool::EBrushMode currentMode = BrushTool::getPreviewModeFromKeys(); - Int modeInt = (Int)currentMode; - Coord3D brushPos = DrawObject::getFeedbackPos(); CPoint viewPt; if (!docToViewCoords(brushPos, &viewPt)) { - if (!m_lastHintRect.IsRectEmpty()) { - m_lastHintRect.SetRectEmpty(); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } + clearBrushModeHintState(); return; } @@ -1138,53 +1126,48 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) hintPos.x += 20; hintPos.y -= 20; - char primaryBuf[256]; - char secondaryBuf[512]; - BrushTool::getModeHintStrings(primaryBuf, sizeof(primaryBuf), secondaryBuf, sizeof(secondaryBuf)); - - const char* firstLine = secondaryBuf && strlen(secondaryBuf) > 0 ? secondaryBuf : primaryBuf; - - Bool needUpdate = false; - if (modeInt != m_lastBrushMode) { - needUpdate = true; - } else if (m_lastHintRect.IsRectEmpty()) { - needUpdate = true; - } else { - Int dx = abs(hintPos.x - m_lastHintPos.x); - Int dy = abs(hintPos.y - m_lastHintPos.y); - if (dx > 15 || dy > 15) { - needUpdate = true; + char hintTextBuf[512]; + BrushTool::BrushHintInfo info; + if (!BrushTool::getBrushHintInfo(info, hintTextBuf, sizeof(hintTextBuf), hintPos, m_lastBrushMode)) { + if (info.shouldClear) { + clearBrushModeHintState(); } + return; } - - if (!firstLine || strlen(firstLine) == 0) { - if (!m_lastHintRect.IsRectEmpty()) { - m_lastHintRect.SetRectEmpty(); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } + + if (!info.shouldShow || strlen(hintTextBuf) == 0) { + clearBrushModeHintState(); return; } - CRect hintRect; - CSize textSize; + const char* firstLine = hintTextBuf; + Int firstLineLen = (Int)strlen(firstLine); + Bool needUpdate = (info.modeInt != m_lastBrushMode); + if (!needUpdate) { + if (m_lastHintRect.IsRectEmpty()) { + needUpdate = true; + } else { + Int dx = abs(hintPos.x - m_lastHintPos.x); + Int dy = abs(hintPos.y - m_lastHintPos.y); + if (dx > 15 || dy > 15) { + needUpdate = true; + } + } + } + + CSize textSize = pDc->GetTextExtent(firstLine, firstLineLen); - textSize = pDc->GetTextExtent(firstLine, (Int)strlen(firstLine)); + if (!needUpdate && !m_lastHintRect.IsRectEmpty()) { + hintPos = m_lastHintPos; + } Int padding = 6; + CRect hintRect; hintRect.left = hintPos.x - padding; hintRect.top = hintPos.y - textSize.cy - padding; hintRect.right = hintPos.x + textSize.cx + padding; hintRect.bottom = hintPos.y + padding; - if (!needUpdate && !m_lastHintRect.IsRectEmpty()) { - hintPos = m_lastHintPos; - hintRect.left = hintPos.x - padding; - hintRect.top = hintPos.y - textSize.cy - padding; - hintRect.right = hintPos.x + textSize.cx + padding; - hintRect.bottom = hintPos.y + padding; - } - if (pUpdateRgn && !needUpdate && !m_lastHintRect.IsRectEmpty()) { CRect adjustedHintRect = hintRect; adjustedHintRect.OffsetRect(-mXScrollOffset, -mYScrollOffset); @@ -1196,7 +1179,7 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) COLORREF oldColor = pDc->SetTextColor(RGB(255, 255, 255)); Int oldBkMode = pDc->SetBkMode(TRANSPARENT); - pDc->TextOut(hintPos.x, hintPos.y - textSize.cy, firstLine, (Int)strlen(firstLine)); + pDc->TextOut(hintPos.x, hintPos.y - textSize.cy, firstLine, firstLineLen); pDc->SetTextColor(oldColor); pDc->SetBkMode(oldBkMode); @@ -1204,15 +1187,13 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) if (needUpdate) { m_lastHintRect = hintRect; m_lastHintPos = hintPos; - m_lastBrushMode = modeInt; + m_lastBrushMode = info.modeInt; } } //============================================================================= // CWorldBuilderView::OnMouseMove //============================================================================= -/** Override to invalidate hint area when mouse moves. */ -//============================================================================= void CWorldBuilderView::OnMouseMove(UINT nFlags, CPoint point) { WbView::OnMouseMove(nFlags, point); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/brushoptions.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/brushoptions.cpp index d8c23682b3..3cf825ecd7 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/brushoptions.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/brushoptions.cpp @@ -92,7 +92,7 @@ void BrushOptions::setHeight(Int height) void BrushOptions::setRaiseLowerAmount(Int amount) { - char buffer[12]; + char buffer[_MAX_PATH]; snprintf(buffer, ARRAY_SIZE(buffer), "%d", amount); m_currentRaiseLower = amount; if (m_staticThis && !m_staticThis->m_updating) { diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp index f982f2169d..d0afe58bcf 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -3273,15 +3273,23 @@ void WbView3d::OnUpdateViewShowSoundCircles(CCmdUI* pCmdUI) //============================================================================= // WbView3d::OnMouseMove //============================================================================= -/** Override to invalidate hint area when mouse moves. */ -//============================================================================= void WbView3d::OnMouseMove(UINT nFlags, CPoint point) { - // Call base class first WbView::OnMouseMove(nFlags, point); - - // Don't invalidate here - let drawLabels handle it naturally - // Invalidating here causes flicker due to paint feedback loops +} + +//============================================================================= +// WbView3d::clearBrushModeHintState +//============================================================================= +/** Clears the brush mode hint state. */ +//============================================================================= +void WbView3d::clearBrushModeHintState() +{ + if (!::IsRectEmpty(&m_lastHintRect)) { + ::SetRectEmpty(&m_lastHintRect); + m_lastBrushMode = -1; + m_lastHintPos = CPoint(-10000, -10000); + } } //============================================================================= @@ -3294,39 +3302,13 @@ void WbView3d::drawBrushModeHint(HDC hdc) if (m_hintDrawnThisFrame) { return; } - - Tool *pCurTool = WbApp()->getCurTool(); - if (!pCurTool || pCurTool->getToolID() != ID_BRUSH_TOOL) { - if (!::IsRectEmpty(&m_lastHintRect)) { - ::SetRectEmpty(&m_lastHintRect); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } - return; - } - - if (::GetAsyncKeyState(VK_LBUTTON) & 0x8000) { - if (!::IsRectEmpty(&m_lastHintRect)) { - ::SetRectEmpty(&m_lastHintRect); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } - return; - } - - BrushTool::EBrushMode currentMode = BrushTool::getPreviewModeFromKeys(); - Int modeInt = (Int)currentMode; Coord3D brushPos = DrawObject::getFeedbackPos(); Vector3 worldPos, screenPos; worldPos.Set(brushPos.x, brushPos.y, brushPos.z); if (CameraClass::INSIDE_FRUSTUM != m_camera->Project(screenPos, worldPos)) { - if (!::IsRectEmpty(&m_lastHintRect)) { - ::SetRectEmpty(&m_lastHintRect); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } + clearBrushModeHintState(); return; } @@ -3339,53 +3321,48 @@ void WbView3d::drawBrushModeHint(HDC hdc) hintPos.x = rClient.left + sx + 20; hintPos.y = rClient.top + sy - 20; - char primaryBuf[256]; - char secondaryBuf[512]; - BrushTool::getModeHintStrings(primaryBuf, sizeof(primaryBuf), secondaryBuf, sizeof(secondaryBuf)); - - const char* firstLine = secondaryBuf && strlen(secondaryBuf) > 0 ? secondaryBuf : primaryBuf; - - Bool needUpdate = false; - if (modeInt != m_lastBrushMode) { - needUpdate = true; - } else if (::IsRectEmpty(&m_lastHintRect)) { - needUpdate = true; - } else { - Int dx = abs(hintPos.x - m_lastHintPos.x); - Int dy = abs(hintPos.y - m_lastHintPos.y); - if (dx > 15 || dy > 15) { - needUpdate = true; + char hintTextBuf[512]; + BrushTool::BrushHintInfo info; + if (!BrushTool::getBrushHintInfo(info, hintTextBuf, sizeof(hintTextBuf), hintPos, m_lastBrushMode)) { + if (info.shouldClear) { + clearBrushModeHintState(); } + return; } - - if (!firstLine || strlen(firstLine) == 0) { - if (!::IsRectEmpty(&m_lastHintRect)) { - ::SetRectEmpty(&m_lastHintRect); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } + + if (!info.shouldShow || strlen(hintTextBuf) == 0) { + clearBrushModeHintState(); return; } + const char* firstLine = hintTextBuf; + Int firstLineLen = (Int)strlen(firstLine); + Bool needUpdate = (info.modeInt != m_lastBrushMode); + if (!needUpdate) { + if (::IsRectEmpty(&m_lastHintRect)) { + needUpdate = true; + } else { + Int dx = abs(hintPos.x - m_lastHintPos.x); + Int dy = abs(hintPos.y - m_lastHintPos.y); + if (dx > 15 || dy > 15) { + needUpdate = true; + } + } + } + RECT hintRect; SIZE textSize; + if (m3DFont && !hdc) { + textSize.cx = firstLineLen * 6; + textSize.cy = 16; + } else { + ::GetTextExtentPoint32(hdc, firstLine, firstLineLen, &textSize); + } + if (!needUpdate && !::IsRectEmpty(&m_lastHintRect)) { hintPos = m_lastHintPos; hintRect = m_lastHintRect; - if (m3DFont && !hdc) { - textSize.cx = (Int)strlen(firstLine) * 6; - textSize.cy = 16; - } else { - ::GetTextExtentPoint32(hdc, firstLine, (Int)strlen(firstLine), &textSize); - } } else { - if (m3DFont && !hdc) { - textSize.cx = (Int)strlen(firstLine) * 6; - textSize.cy = 16; - } else { - ::GetTextExtentPoint32(hdc, firstLine, (Int)strlen(firstLine), &textSize); - } - Int padding = 6; hintRect.left = hintPos.x - padding; hintRect.top = hintPos.y - textSize.cy - padding; @@ -3396,19 +3373,19 @@ void WbView3d::drawBrushModeHint(HDC hdc) if (m3DFont && !hdc) { RECT textRect = hintRect; textRect.bottom = textRect.top + textSize.cy; - m3DFont->DrawText(firstLine, (Int)strlen(firstLine), &textRect, + m3DFont->DrawText(firstLine, firstLineLen, &textRect, DT_LEFT | DT_NOCLIP | DT_TOP | DT_SINGLELINE, 0xFFFFFFFF); } else if (hdc) { ::SetBkMode(hdc, TRANSPARENT); ::SetTextColor(hdc, RGB(255, 255, 255)); - ::TextOut(hdc, hintPos.x, hintPos.y - textSize.cy, firstLine, (Int)strlen(firstLine)); + ::TextOut(hdc, hintPos.x, hintPos.y - textSize.cy, firstLine, firstLineLen); } if (needUpdate) { m_lastHintRect = hintRect; m_lastHintPos = hintPos; - m_lastBrushMode = modeInt; + m_lastBrushMode = info.modeInt; } m_hintDrawnThisFrame = true; From dc80a61ba2682f5861a9e4813ec4ba98a91af603 Mon Sep 17 00:00:00 2001 From: Dimitris Kalyvas Date: Mon, 1 Dec 2025 23:30:00 +0200 Subject: [PATCH 6/7] refactor(worldbuilder): refactored brush and wbview files Used clamp in more instances Created helper functions Simplified parts of the code & eliminated duplicates --- .../Tools/WorldBuilder/include/BrushTool.h | 28 ++ .../Code/Tools/WorldBuilder/src/BrushTool.cpp | 252 ++++++++---------- .../Tools/WorldBuilder/src/FeatherTool.cpp | 68 +---- .../Code/Tools/WorldBuilder/src/wbview.cpp | 5 +- 4 files changed, 156 insertions(+), 197 deletions(-) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h b/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h index 69c91f0e88..088e7ad2e8 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/BrushTool.h @@ -108,6 +108,34 @@ class BrushTool : public Tool Int minRadius, Int maxRadius, CWorldBuilderDoc *pDoc); + + // Shared brush iteration with rate accumulation - returns true if rate buffer overflow occurred + static Bool applyBrushWithRateAccumulation( + WorldHeightMapEdit *editMap, + WorldHeightMapEdit *featherMap, + WorldHeightMapEdit *rateMap, + const CPoint &ndx, + Int brushWidth, + Int rateMultiplier, + Int smoothRadius, + Int minRadius, + Int maxRadius, + CWorldBuilderDoc *pDoc); + + // Shared rate buffer reset + static void resetSmoothingBuffers( + WorldHeightMapEdit *editMap, + WorldHeightMapEdit *featherMap, + WorldHeightMapEdit *rateMap); + + // Helper to create a partial range for height map updates + static IRegion2D makePartialRange(const CPoint &ndx, Int brushWidth); + + // Helper to calculate blend factor (square or round) + static Real calcBlendFactor(const CPoint &ndx, Int i, Int j, Int width, Int feather, Bool square); + + // Helper to get effective brush width including feather + static Int getEffectiveBrushWidth(Int baseWidth, Int feather); public: virtual void mouseDown(TTrackingMode m, CPoint viewPt, WbView* pView, CWorldBuilderDoc *pDoc); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/BrushTool.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/BrushTool.cpp index ac01f0e5ab..1eeca6952d 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/BrushTool.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/BrushTool.cpp @@ -32,6 +32,7 @@ #include "brushoptions.h" #include "DrawObject.h" #include "WorldBuilder.h" +#include "Lib/BaseType.h" // // BrushTool class. // @@ -87,33 +88,30 @@ void BrushTool::setHeight(Int height) void BrushTool::setRaiseLowerAmount(Int amount) { - if (amount < MIN_RAISE_LOWER) amount = MIN_RAISE_LOWER; - if (amount > MAX_RAISE_LOWER) amount = MAX_RAISE_LOWER; + amount = clamp((Int)MIN_RAISE_LOWER, amount, (Int)MAX_RAISE_LOWER); if (m_raiseLowerAmount != amount) { m_raiseLowerAmount = amount; BrushOptions::setRaiseLowerAmount(amount); } -}; +} void BrushTool::setSmoothRadius(Int radius) { - if (radius < MIN_SMOOTH_RADIUS) radius = MIN_SMOOTH_RADIUS; - if (radius > MAX_SMOOTH_RADIUS) radius = MAX_SMOOTH_RADIUS; + radius = clamp((Int)MIN_SMOOTH_RADIUS, radius, (Int)MAX_SMOOTH_RADIUS); if (m_smoothRadius != radius) { m_smoothRadius = radius; BrushOptions::setSmoothRadius(radius); } -}; +} void BrushTool::setSmoothRate(Int rate) { - if (rate < MIN_SMOOTH_RATE) rate = MIN_SMOOTH_RATE; - if (rate > MAX_SMOOTH_RATE) rate = MAX_SMOOTH_RATE; + rate = clamp((Int)MIN_SMOOTH_RATE, rate, (Int)MAX_SMOOTH_RATE); if (m_smoothRate != rate) { m_smoothRate = rate; BrushOptions::setSmoothRate(rate); } -}; +} /// Set the brush width and notify the height options panel of the change. void BrushTool::setWidth(Int width) @@ -217,26 +215,17 @@ void BrushTool::mouseMoved(TTrackingMode m, CPoint viewPt, WbView* pView, CWorld switch (m_activeMode) { case BRUSH_MODE_RAISE: case BRUSH_MODE_LOWER: { - Int brushWidth = m_brushWidth; - if (m_brushFeather>0) { - brushWidth += 2*m_brushFeather; - } - brushWidth += 2; + Int brushWidth = getEffectiveBrushWidth(m_brushWidth, m_brushFeather); applyRaiseLowerBrush(ndx, brushWidth, (m_activeMode == BRUSH_MODE_RAISE), pDoc); break; } case BRUSH_MODE_SET: { - Int brushWidth = m_brushWidth; - if (m_brushFeather>0) { - brushWidth += 2*m_brushFeather; - } - brushWidth += 2; + Int brushWidth = getEffectiveBrushWidth(m_brushWidth, m_brushFeather); applySetHeightBrush(ndx, brushWidth, pDoc); break; } case BRUSH_MODE_SMOOTH: { - Int brushWidth = m_brushWidth; - if (brushWidth < 1) brushWidth = 1; + Int brushWidth = max(1, m_brushWidth); applySmoothBrush(ndx, brushWidth, pDoc); break; } @@ -245,19 +234,9 @@ void BrushTool::mouseMoved(TTrackingMode m, CPoint viewPt, WbView* pView, CWorld BrushTool::EBrushMode BrushTool::determineBrushMode() const { - Bool ctrl = (0x8000 & ::GetAsyncKeyState(VK_CONTROL)) != 0; Bool shift = (0x8000 & ::GetAsyncKeyState(VK_SHIFT)) != 0; - - if (ctrl) { - if (shift) { - return BRUSH_MODE_SMOOTH; - } - return BRUSH_MODE_SET; - } - if (shift) { - return BRUSH_MODE_LOWER; - } - return BRUSH_MODE_RAISE; + Bool ctrl = (0x8000 & ::GetAsyncKeyState(VK_CONTROL)) != 0; + return getModeFromModifiers(shift, ctrl); } void BrushTool::applySetHeightBrush(const CPoint &ndx, Int brushWidth, CWorldBuilderDoc *pDoc) @@ -276,12 +255,7 @@ void BrushTool::applySetHeightBrush(const CPoint &ndx, Int brushWidth, CWorldBui if (j<0 || j>=m_htMapEditCopy->getYExtent()) { continue; } - Real blendFactor; - if (m_brushSquare) { - blendFactor = calcSquareBlendFactor(ndx, i, j, m_brushWidth, m_brushFeather); - } else { - blendFactor = calcRoundBlendFactor(ndx, i, j, m_brushWidth, m_brushFeather); - } + Real blendFactor = calcBlendFactor(ndx, i, j, m_brushWidth, m_brushFeather, m_brushSquare); Int curHeight = m_htMapFeatherCopy->getHeight(i,j); float fNewHeight = blendFactor*m_brushHeight+((1.0f-blendFactor)*curHeight); Int newHeight = floor(fNewHeight+0.5f); @@ -298,12 +272,7 @@ void BrushTool::applySetHeightBrush(const CPoint &ndx, Int brushWidth, CWorldBui } } - IRegion2D partialRange; - partialRange.lo.x = ndx.x - brushWidth; - partialRange.hi.x = ndx.x + brushWidth; - partialRange.lo.y = ndx.y - brushWidth; - partialRange.hi.y = ndx.y + brushWidth; - pDoc->updateHeightMap(m_htMapEditCopy, true, partialRange); + pDoc->updateHeightMap(m_htMapEditCopy, true, makePartialRange(ndx, brushWidth)); } void BrushTool::applyRaiseLowerBrush(const CPoint &ndx, Int brushWidth, Bool raising, CWorldBuilderDoc *pDoc) @@ -328,80 +297,28 @@ void BrushTool::applyRaiseLowerBrush(const CPoint &ndx, Int brushWidth, Bool rai if (j<0 || j>=m_htMapEditCopy->getYExtent()) { continue; } - Real blendFactor; - if (m_brushSquare) { - blendFactor = calcSquareBlendFactor(ndx, i, j, m_brushWidth, m_brushFeather); - } else { - blendFactor = calcRoundBlendFactor(ndx, i, j, m_brushWidth, m_brushFeather); - } + Real blendFactor = calcBlendFactor(ndx, i, j, m_brushWidth, m_brushFeather, m_brushSquare); Int curHeight = m_htMapEditCopy->getHeight(i,j); float fNewHeight = (blendFactor*(htDelta+curHeight))+((1.0f-blendFactor)*curHeight); Int newHeight = floor(fNewHeight+0.5f); - - if (newHeight < m_htMapFeatherCopy->getMinHeightValue()) newHeight = m_htMapFeatherCopy->getMinHeightValue(); - if (newHeight > m_htMapFeatherCopy->getMaxHeightValue()) newHeight = m_htMapFeatherCopy->getMaxHeightValue(); + newHeight = clamp(m_htMapFeatherCopy->getMinHeightValue(), newHeight, m_htMapFeatherCopy->getMaxHeightValue()); m_htMapEditCopy->setHeight(i, j, newHeight); pDoc->invalCell(i, j); } } - IRegion2D partialRange; - partialRange.lo.x = ndx.x - brushWidth; - partialRange.hi.x = ndx.x + brushWidth; - partialRange.lo.y = ndx.y - brushWidth; - partialRange.hi.y = ndx.y + brushWidth; - pDoc->updateHeightMap(m_htMapEditCopy, true, partialRange); + pDoc->updateHeightMap(m_htMapEditCopy, true, makePartialRange(ndx, brushWidth)); } void BrushTool::applySmoothBrush(const CPoint &ndx, Int brushWidth, CWorldBuilderDoc *pDoc) { - if (!m_htMapEditCopy || !m_htMapFeatherCopy || !m_htMapRateCopy) return; + Bool redoRate = applyBrushWithRateAccumulation( + m_htMapEditCopy, m_htMapFeatherCopy, m_htMapRateCopy, + ndx, brushWidth, m_smoothRate * 5, + m_smoothRadius, MIN_SMOOTH_RADIUS, MAX_SMOOTH_RADIUS, pDoc); - int sub = brushWidth/2; - int add = brushWidth-sub; - - Int i, j; - Bool redoRate = false; - - for (i= ndx.x-sub; i< ndx.x+add; i++) { - if (i<0 || i>=m_htMapEditCopy->getXExtent()) { - continue; - } - for (j=ndx.y-sub; j=m_htMapEditCopy->getYExtent()) { - continue; - } - Real blendFactor = calcRoundBlendFactor(ndx, i, j, brushWidth, 0); - - if (blendFactor > 0.0f) { - Int rate = m_htMapRateCopy->getHeight(i, j); - rate += blendFactor * m_smoothRate*5; - if (rate>255) { - rate = 255; - redoRate = true; - } - m_htMapRateCopy->setHeight(i,j,rate); - - applySmoothingAlgorithm( - m_htMapEditCopy, - m_htMapFeatherCopy, - i, j, - rate, - m_smoothRadius, - MIN_SMOOTH_RADIUS, - MAX_SMOOTH_RADIUS, - pDoc); - } - } - } - - IRegion2D partialRange; - partialRange.lo.x = ndx.x - brushWidth; - partialRange.hi.x = ndx.x + brushWidth; - partialRange.lo.y = ndx.y - brushWidth; - partialRange.hi.y = ndx.y + brushWidth; - pDoc->updateHeightMap(m_htMapEditCopy, true, partialRange); + pDoc->updateHeightMap(m_htMapEditCopy, true, makePartialRange(ndx, brushWidth)); if (redoRate) { resetSmoothRateBuffer(); @@ -410,16 +327,7 @@ void BrushTool::applySmoothBrush(const CPoint &ndx, Int brushWidth, CWorldBuilde void BrushTool::resetSmoothRateBuffer() { - if (!m_htMapRateCopy || !m_htMapFeatherCopy || !m_htMapEditCopy) return; - - Int size = m_htMapRateCopy->getXExtent() * m_htMapRateCopy->getYExtent(); - UnsignedByte *pRate = m_htMapRateCopy->getDataPtr(); - UnsignedByte *pFeather = m_htMapFeatherCopy->getDataPtr(); - UnsignedByte *pEdit = m_htMapEditCopy->getDataPtr(); - for (Int idx=0; idx maxRadius) radius = maxRadius; + Int radius = clamp(minRadius, smoothRadius, maxRadius); Int total = 0; Real numSamples = 0; @@ -568,24 +474,12 @@ void BrushTool::applySmoothingAlgorithm( factor = 1.0f; } else { Real dist = sqrt((Real)((ii - i) * (ii - i) + (jj - j) * (jj - j))); - if (dist < 1.0f) dist = 1.0f; - if (dist > radius) { - factor = 0; - } else { - factor = 1.0f - (dist - 1) / radius; - } + dist = max(1.0f, dist); + factor = (dist > radius) ? 0.0f : 1.0f - (dist - 1) / radius; } - int iNdx = ii; - if (iNdx < 0) iNdx = 1; - if (iNdx >= editMap->getXExtent()) { - iNdx = editMap->getXExtent() - 1; - } - int jNdx = jj; - if (jNdx < 0) jNdx = 1; - if (jNdx >= editMap->getYExtent()) { - jNdx = editMap->getYExtent() - 1; - } + int iNdx = clamp(0, ii, editMap->getXExtent() - 1); + int jNdx = clamp(0, jj, editMap->getYExtent() - 1); total += featherMap->getHeight(iNdx, jNdx); numSamples += 1; @@ -599,3 +493,93 @@ void BrushTool::applySmoothingAlgorithm( editMap->setHeight(i, j, total); pDoc->invalCell(i, j); } + +Bool BrushTool::applyBrushWithRateAccumulation( + WorldHeightMapEdit *editMap, + WorldHeightMapEdit *featherMap, + WorldHeightMapEdit *rateMap, + const CPoint &ndx, + Int brushWidth, + Int rateMultiplier, + Int smoothRadius, + Int minRadius, + Int maxRadius, + CWorldBuilderDoc *pDoc) +{ + if (!editMap || !featherMap || !rateMap) return false; + + Int sub = brushWidth / 2; + Int add = brushWidth - sub; + Bool rateOverflow = false; + Int xExtent = editMap->getXExtent(); + Int yExtent = editMap->getYExtent(); + + for (Int i = ndx.x - sub; i < ndx.x + add; i++) { + if (i < 0 || i >= xExtent) continue; + + for (Int j = ndx.y - sub; j < ndx.y + add; j++) { + if (j < 0 || j >= yExtent) continue; + + Real blendFactor = calcRoundBlendFactor(ndx, i, j, brushWidth, 0); + if (blendFactor <= 0.0f) continue; + + Int rate = rateMap->getHeight(i, j); + rate += (Int)(blendFactor * rateMultiplier); + if (rate > 255) { + rate = 255; + rateOverflow = true; + } + rateMap->setHeight(i, j, rate); + + applySmoothingAlgorithm(editMap, featherMap, i, j, rate, + smoothRadius, minRadius, maxRadius, pDoc); + } + } + + return rateOverflow; +} + +void BrushTool::resetSmoothingBuffers( + WorldHeightMapEdit *editMap, + WorldHeightMapEdit *featherMap, + WorldHeightMapEdit *rateMap) +{ + if (!editMap || !featherMap || !rateMap) return; + + Int size = rateMap->getXExtent() * rateMap->getYExtent(); + UnsignedByte *pRate = rateMap->getDataPtr(); + UnsignedByte *pFeather = featherMap->getDataPtr(); + UnsignedByte *pEdit = editMap->getDataPtr(); + + for (Int i = 0; i < size; i++) { + *pRate++ = 0; + *pFeather++ = *pEdit++; + } +} + +IRegion2D BrushTool::makePartialRange(const CPoint &ndx, Int brushWidth) +{ + IRegion2D range; + range.lo.x = ndx.x - brushWidth; + range.hi.x = ndx.x + brushWidth; + range.lo.y = ndx.y - brushWidth; + range.hi.y = ndx.y + brushWidth; + return range; +} + +Real BrushTool::calcBlendFactor(const CPoint &ndx, Int i, Int j, Int width, Int feather, Bool square) +{ + if (square) { + return calcSquareBlendFactor(ndx, i, j, width, feather); + } + return calcRoundBlendFactor(ndx, i, j, width, feather); +} + +Int BrushTool::getEffectiveBrushWidth(Int baseWidth, Int feather) +{ + Int width = baseWidth; + if (feather > 0) { + width += 2 * feather; + } + return width + 2; +} diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp index c4d9c0ddd5..6351be6d86 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp @@ -145,74 +145,20 @@ void FeatherTool::mouseMoved(TTrackingMode m, CPoint viewPt, WbView* pView, CWor DrawObject::setFeedbackPos(cpt); if (m != TRACK_L) return; - int brushWidth = m_feather; - CPoint ndx; getCenterIndex(&cpt, m_feather, &ndx, pDoc); - //if (m_prevXIndex == ndx.x && m_prevYIndex == ndx.y) return; - m_prevXIndex = ndx.x; m_prevYIndex = ndx.y; - int sub = brushWidth/2; - int add = brushWidth-sub; - - // round brush - Int i, j; - Bool redoRate = false; - - for (i= ndx.x-sub; i< ndx.x+add; i++) { - if (i<0 || i>=m_htMapEditCopy->getXExtent()) { - continue; - } - for (j=ndx.y-sub; j=m_htMapEditCopy->getYExtent()) { - continue; - } - Real blendFactor; - blendFactor = calcRoundBlendFactor(ndx, i, j, m_feather, 0); - // m_htMapEditCopy is the output. - // m_htMapFeatherCopy is the original input. - // m_htMapRateCopy is how much we use of the feathered data. - if (blendFactor > 0.0f) { - Int rate = m_htMapRateCopy->getHeight(i, j); - rate += blendFactor * m_rate*5; - if (rate>255) { - rate = 255; - redoRate = true; - } - m_htMapRateCopy->setHeight(i,j,rate); - - BrushTool::applySmoothingAlgorithm( - m_htMapEditCopy, - m_htMapFeatherCopy, - i, j, - rate, - m_radius, - 1, - FeatherOptions::MAX_RADIUS, - pDoc); - } - } - } + Bool redoRate = BrushTool::applyBrushWithRateAccumulation( + m_htMapEditCopy, m_htMapFeatherCopy, m_htMapRateCopy, + ndx, m_feather, m_rate * 5, + m_radius, 1, FeatherOptions::MAX_RADIUS, pDoc); - IRegion2D partialRange; - partialRange.lo.x = ndx.x - brushWidth; - partialRange.hi.x = ndx.x + brushWidth; - partialRange.lo.y = ndx.y - brushWidth; - partialRange.hi.y = ndx.y + brushWidth; - pDoc->updateHeightMap(m_htMapEditCopy, true, partialRange); + pDoc->updateHeightMap(m_htMapEditCopy, true, BrushTool::makePartialRange(ndx, m_feather)); + if (redoRate) { - Int size = m_htMapRateCopy->getXExtent() * m_htMapRateCopy->getYExtent(); - UnsignedByte *pData = m_htMapRateCopy->getDataPtr(); - UnsignedByte *pFeather = m_htMapFeatherCopy->getDataPtr(); - UnsignedByte *pEdit = m_htMapEditCopy->getDataPtr(); - Int i; - for (i=0; i 0 ? 1 : -1; - Int newWidth = max(1, min(100, currentWidth + delta)); + Int newWidth = clamp(1, currentWidth + delta, 100); BrushTool::setWidth(newWidth); CString statusText; @@ -1118,7 +1119,7 @@ Bool WbView::handleBrushMouseWheel(UINT nFlags, short zDelta) } else if (ctrlDown && !shiftDown) { Int currentFeather = BrushTool::getFeather(); Int delta = zDelta > 0 ? 1 : -1; - Int newFeather = max(0, min(100, currentFeather + delta)); + Int newFeather = clamp(0, currentFeather + delta, 100); BrushTool::setFeather(newFeather); CString statusText; From f57f9bd408eda5ef964190ecd6af6831f83b6571 Mon Sep 17 00:00:00 2001 From: Dimitris Kalyvas Date: Tue, 2 Dec 2025 02:28:02 +0200 Subject: [PATCH 7/7] refactor(worldbuilder): refactor HintDrawState for future expansion Turned the 4 previous variables into a struct HintDrawState which should be able to expand for future hint usage in the editor. Ensured functionality remains identical and does not have flickering. --- .../WorldBuilder/include/WorldBuilderView.h | 6 +-- .../Code/Tools/WorldBuilder/include/wbview.h | 43 +++++++++++++++++ .../Tools/WorldBuilder/include/wbview3d.h | 6 +-- .../WorldBuilder/src/WorldBuilderView.cpp | 39 ++++----------- .../Code/Tools/WorldBuilder/src/wbview3d.cpp | 47 +++++-------------- 5 files changed, 67 insertions(+), 74 deletions(-) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h b/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h index ba6e91b1e3..639a36ed79 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/WorldBuilderView.h @@ -73,11 +73,7 @@ class CWorldBuilderView : public WbView CPoint m_scrollMin; ///< The minimum scrollbar positions. CPoint m_scrollMax; ///< The maximum scroll bar positions. - // Brush mode hint flicker reduction - Int m_lastBrushMode; ///< Last brush mode drawn (for flicker reduction) - CRect m_lastHintRect; ///< Last hint rectangle drawn (for flicker reduction) - CPoint m_lastHintPos; ///< Last hint position (for flicker reduction) - Bool m_hintDrawnThisPaint; ///< Flag to prevent double-drawing in same paint cycle + HintDrawState m_brushHintState; ///< Flicker reduction state for brush mode hint protected: diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview.h b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview.h index 5f4ba0074b..056f8bdde5 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview.h @@ -40,6 +40,49 @@ enum RulerTypeEnum { RULER_CIRCLE, }; +///////////////////////////////////////////////////////////////////////////// +// HintDrawState - reusable flicker reduction state for on-screen text hints +// +// Usage: +// 1. Call beginFrame() at start of each paint/render cycle +// 2. Call needsUpdate() to check if hint position changed significantly +// 3. Call commitUpdate() after drawing to store the new state +// 4. Call reset() when hint should be hidden + +struct HintDrawState { + Int lastMode; + CRect lastRect; + CPoint lastPos; + Bool drawnThisFrame; + + HintDrawState() : lastMode(-1), lastPos(-10000, -10000), drawnThisFrame(false) {} + + void reset() { + if (!lastRect.IsRectEmpty()) { + lastRect.SetRectEmpty(); + lastMode = -1; + lastPos = CPoint(-10000, -10000); + } + } + + void beginFrame() { drawnThisFrame = false; } + + bool needsUpdate(CPoint newPos, Int newMode, Int threshold = 15) const { + if (newMode != lastMode) return true; + if (lastRect.IsRectEmpty()) return true; + Int dx = abs(newPos.x - lastPos.x); + Int dy = abs(newPos.y - lastPos.y); + return (dx > threshold || dy > threshold); + } + + void commitUpdate(CPoint pos, const CRect& rect, Int mode) { + lastPos = pos; + lastRect = rect; + lastMode = mode; + drawnThisFrame = true; + } +}; + ///////////////////////////////////////////////////////////////////////////// // WbView view diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h index daff7b754d..1a8f62e501 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h @@ -217,11 +217,7 @@ class WbView3d : public WbView, public DX8_CleanupHook Int m_pickPixels; Int m_partialMapSize; - // Brush mode hint flicker reduction - Int m_lastBrushMode; ///< Last brush mode drawn (for flicker reduction) - RECT m_lastHintRect; ///< Last hint rectangle drawn (for flicker reduction) - CPoint m_lastHintPos; ///< Last hint position (for flicker reduction) - Bool m_hintDrawnThisFrame; ///< Flag to prevent double-drawing in same frame + HintDrawState m_brushHintState; ///< Flicker reduction state for brush mode hint protected: diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp index a61b9dfac4..159e6c763c 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderView.cpp @@ -81,11 +81,7 @@ CWorldBuilderView::CWorldBuilderView() : m_scrollMin(0,0), m_scrollMax(0,0), mShowGrid(true), - m_showTexture(true), - m_lastBrushMode(-1), - m_lastHintRect(0,0,0,0), - m_lastHintPos(-10000, -10000), - m_hintDrawnThisPaint(false) + m_showTexture(true) { Int show; show = ::AfxGetApp()->GetProfileInt(MAIN_FRAME_SECTION, "ShowContours", 0); @@ -315,7 +311,7 @@ void CWorldBuilderView::OnPaint() CPaintDC dc(this); // device context for painting - m_hintDrawnThisPaint = false; + m_brushHintState.beginFrame(); // Offset the origin so that we can draw on 0 based coordinates. dc.SetViewportOrg(-mXScrollOffset, -mYScrollOffset); updateRgn.OffsetRgn(mXScrollOffset, mYScrollOffset); @@ -1098,11 +1094,7 @@ DEBUG_ASSERTCRASH((abs(curPt.y-curPt2.y)<1),("oops")); //============================================================================= void CWorldBuilderView::clearBrushModeHintState() { - if (!m_lastHintRect.IsRectEmpty()) { - m_lastHintRect.SetRectEmpty(); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } + m_brushHintState.reset(); } //============================================================================= @@ -1128,7 +1120,7 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) char hintTextBuf[512]; BrushTool::BrushHintInfo info; - if (!BrushTool::getBrushHintInfo(info, hintTextBuf, sizeof(hintTextBuf), hintPos, m_lastBrushMode)) { + if (!BrushTool::getBrushHintInfo(info, hintTextBuf, sizeof(hintTextBuf), hintPos, m_brushHintState.lastMode)) { if (info.shouldClear) { clearBrushModeHintState(); } @@ -1142,23 +1134,12 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) const char* firstLine = hintTextBuf; Int firstLineLen = (Int)strlen(firstLine); - Bool needUpdate = (info.modeInt != m_lastBrushMode); - if (!needUpdate) { - if (m_lastHintRect.IsRectEmpty()) { - needUpdate = true; - } else { - Int dx = abs(hintPos.x - m_lastHintPos.x); - Int dy = abs(hintPos.y - m_lastHintPos.y); - if (dx > 15 || dy > 15) { - needUpdate = true; - } - } - } + Bool needUpdate = m_brushHintState.needsUpdate(hintPos, info.modeInt); CSize textSize = pDc->GetTextExtent(firstLine, firstLineLen); - if (!needUpdate && !m_lastHintRect.IsRectEmpty()) { - hintPos = m_lastHintPos; + if (!needUpdate && !m_brushHintState.lastRect.IsRectEmpty()) { + hintPos = m_brushHintState.lastPos; } Int padding = 6; @@ -1168,7 +1149,7 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) hintRect.right = hintPos.x + textSize.cx + padding; hintRect.bottom = hintPos.y + padding; - if (pUpdateRgn && !needUpdate && !m_lastHintRect.IsRectEmpty()) { + if (pUpdateRgn && !needUpdate && !m_brushHintState.lastRect.IsRectEmpty()) { CRect adjustedHintRect = hintRect; adjustedHintRect.OffsetRect(-mXScrollOffset, -mYScrollOffset); if (!pUpdateRgn->RectInRegion(&adjustedHintRect)) { @@ -1185,9 +1166,7 @@ void CWorldBuilderView::drawBrushModeHint(CDC *pDc, CRgn *pUpdateRgn) pDc->SetBkMode(oldBkMode); if (needUpdate) { - m_lastHintRect = hintRect; - m_lastHintPos = hintPos; - m_lastBrushMode = info.modeInt; + m_brushHintState.commitUpdate(hintPos, hintRect, info.modeInt); } } diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp index d0afe58bcf..6ddb2b5c5a 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -407,12 +407,8 @@ WbView3d::WbView3d() : m_showWeaponRanges(false), m_highlightTestArt(false), m_showLetterbox(false), - m_showSoundCircles(false), - m_lastBrushMode(-1), - m_lastHintPos(-10000, -10000), - m_hintDrawnThisFrame(false) + m_showSoundCircles(false) { - ::SetRectEmpty(&m_lastHintRect); TheTacticalView = &bogusTacticalView; m_actualWinSize.x = ::AfxGetApp()->GetProfileInt(MAIN_FRAME_SECTION, "Width", THREE_D_VIEW_WIDTH); m_actualWinSize.y = ::AfxGetApp()->GetProfileInt(MAIN_FRAME_SECTION, "Height", THREE_D_VIEW_HEIGHT); @@ -2359,7 +2355,7 @@ int WbView3d::OnCreate(LPCREATESTRUCT lpCreateStruct) void WbView3d::OnPaint() { // Reset hint drawn flag at start of paint cycle to prevent double-drawing - m_hintDrawnThisFrame = false; + m_brushHintState.beginFrame(); PAINTSTRUCT ps; HDC hdc = ::BeginPaint(m_hWnd, &ps); @@ -3285,11 +3281,7 @@ void WbView3d::OnMouseMove(UINT nFlags, CPoint point) //============================================================================= void WbView3d::clearBrushModeHintState() { - if (!::IsRectEmpty(&m_lastHintRect)) { - ::SetRectEmpty(&m_lastHintRect); - m_lastBrushMode = -1; - m_lastHintPos = CPoint(-10000, -10000); - } + m_brushHintState.reset(); } //============================================================================= @@ -3299,7 +3291,7 @@ void WbView3d::clearBrushModeHintState() //============================================================================= void WbView3d::drawBrushModeHint(HDC hdc) { - if (m_hintDrawnThisFrame) { + if (m_brushHintState.drawnThisFrame) { return; } @@ -3323,7 +3315,7 @@ void WbView3d::drawBrushModeHint(HDC hdc) char hintTextBuf[512]; BrushTool::BrushHintInfo info; - if (!BrushTool::getBrushHintInfo(info, hintTextBuf, sizeof(hintTextBuf), hintPos, m_lastBrushMode)) { + if (!BrushTool::getBrushHintInfo(info, hintTextBuf, sizeof(hintTextBuf), hintPos, m_brushHintState.lastMode)) { if (info.shouldClear) { clearBrushModeHintState(); } @@ -3337,20 +3329,8 @@ void WbView3d::drawBrushModeHint(HDC hdc) const char* firstLine = hintTextBuf; Int firstLineLen = (Int)strlen(firstLine); - Bool needUpdate = (info.modeInt != m_lastBrushMode); - if (!needUpdate) { - if (::IsRectEmpty(&m_lastHintRect)) { - needUpdate = true; - } else { - Int dx = abs(hintPos.x - m_lastHintPos.x); - Int dy = abs(hintPos.y - m_lastHintPos.y); - if (dx > 15 || dy > 15) { - needUpdate = true; - } - } - } + Bool needUpdate = m_brushHintState.needsUpdate(hintPos, info.modeInt); - RECT hintRect; SIZE textSize; if (m3DFont && !hdc) { textSize.cx = firstLineLen * 6; @@ -3359,9 +3339,10 @@ void WbView3d::drawBrushModeHint(HDC hdc) ::GetTextExtentPoint32(hdc, firstLine, firstLineLen, &textSize); } - if (!needUpdate && !::IsRectEmpty(&m_lastHintRect)) { - hintPos = m_lastHintPos; - hintRect = m_lastHintRect; + CRect hintRect; + if (!needUpdate && !m_brushHintState.lastRect.IsRectEmpty()) { + hintPos = m_brushHintState.lastPos; + hintRect = m_brushHintState.lastRect; } else { Int padding = 6; hintRect.left = hintPos.x - padding; @@ -3383,10 +3364,8 @@ void WbView3d::drawBrushModeHint(HDC hdc) } if (needUpdate) { - m_lastHintRect = hintRect; - m_lastHintPos = hintPos; - m_lastBrushMode = info.modeInt; + m_brushHintState.commitUpdate(hintPos, hintRect, info.modeInt); + } else { + m_brushHintState.drawnThisFrame = true; } - - m_hintDrawnThisFrame = true; }