diff --git a/neo/engine/renderer/RenderWorld.h b/neo/engine/renderer/RenderWorld.h index 22c3b51e..6dc11ee7 100644 --- a/neo/engine/renderer/RenderWorld.h +++ b/neo/engine/renderer/RenderWorld.h @@ -832,6 +832,7 @@ public: // Screen-space world decals consumed by the path tracer. virtual void ClearWorldDecals() = 0; virtual int AddWorldDecal(const renderWorldDecal_t& decal) = 0; + virtual bool UpdateWorldDecal(int decalIndex, const renderWorldDecal_t& decal) = 0; virtual void RemoveWorldDecal(int decalIndex) = 0; virtual int NumWorldDecals() const = 0; virtual const renderWorldDecal_t* GetWorldDecal(int decalIndex) const = 0; diff --git a/neo/engine/renderer/RenderWorld_load.cpp b/neo/engine/renderer/RenderWorld_load.cpp index a13c7f83..c2f626b3 100644 --- a/neo/engine/renderer/RenderWorld_load.cpp +++ b/neo/engine/renderer/RenderWorld_load.cpp @@ -142,6 +142,16 @@ int idRenderWorldLocal::AddWorldDecal(const renderWorldDecal_t& inDecal) { return worldDecals.Append(decal); } +bool idRenderWorldLocal::UpdateWorldDecal(int decalIndex, const renderWorldDecal_t& inDecal) { + if (decalIndex < 0 || decalIndex >= worldDecals.Num()) { + return false; + } + renderWorldDecal_t decal = inDecal; + R_NormalizeDecalBasis(decal); + worldDecals[decalIndex] = decal; + return true; +} + void idRenderWorldLocal::RemoveWorldDecal(int decalIndex) { if (decalIndex >= 0 && decalIndex < worldDecals.Num()) { worldDecals.RemoveIndex(decalIndex); diff --git a/neo/engine/renderer/RenderWorld_local.h b/neo/engine/renderer/RenderWorld_local.h index 7b03258f..6e96c534 100644 --- a/neo/engine/renderer/RenderWorld_local.h +++ b/neo/engine/renderer/RenderWorld_local.h @@ -125,6 +125,7 @@ public: virtual bool FastWorldTrace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; virtual void ClearWorldDecals(); virtual int AddWorldDecal( const renderWorldDecal_t &decal ); + virtual bool UpdateWorldDecal( int decalIndex, const renderWorldDecal_t &decal ); virtual void RemoveWorldDecal( int decalIndex ); virtual int NumWorldDecals() const; virtual const renderWorldDecal_t *GetWorldDecal( int decalIndex ) const; diff --git a/neo/engine/tools/radiant/CamWnd.cpp b/neo/engine/tools/radiant/CamWnd.cpp index 08a76535..d6c962cf 100644 --- a/neo/engine/tools/radiant/CamWnd.cpp +++ b/neo/engine/tools/radiant/CamWnd.cpp @@ -120,6 +120,9 @@ struct cameraNavState_t { static cameraNavConfig_t s_cameraNavConfig; static cameraNavState_t s_cameraNavState; static void CameraNav_GetIniPath(char* path, int pathSize); +static bool s_camWndDecalPreviewValid = false; +static HWND s_camWndDecalPreviewOwner = NULL; +static renderWorldDecal_t s_camWndDecalPreview; static bool CamWnd_DecalPlacementEnabled() { char iniPath[MAX_PATH]; @@ -135,13 +138,31 @@ static float CamWnd_DecalFloat(const char* key, const char* defaultValue) { return (float)atof(value); } -static void CamWnd_DecalMaterial(idStr& material) { +static float s_camWndDecalPreviewRotation = 0.0f; + +static void CamWnd_RotateDecalBasis(renderWorldDecal_t& decal, float degrees) { + const float radians = degrees * idMath::M_DEG2RAD; + const float s = sin(radians); + const float c = cos(radians); + const idVec3 oldU = decal.axisU; + const idVec3 oldV = decal.axisV; + decal.axisU = oldU * c + oldV * s; + decal.axisV = oldV * c - oldU * s; +} + +static bool CamWnd_DecalMaterial(idStr& material) { const char* selected = g_qeglobals.d_texturewin.texdef.name; - if (selected && selected[0] && selected[0] != '(') { - material = selected; - return; + if (!selected || !selected[0] || selected[0] == '(') { + return false; } - material = "textures/decals/default"; + + const idMaterial* mat = declManager->FindMaterial(selected, false); + if (!mat || mat->TestMaterialFlag(MF_DEFAULTED)) { + return false; + } + + material = selected; + return true; } static void CamWnd_DecalColorFromSelectedMaterial(const idStr& material, idVec4& color) { @@ -1074,6 +1095,10 @@ static const char* CAMWND_PROP_DECAL_EDITING = "QER_CamWnd_DecalEditing"; #define CAMWND_MENU_CMD_SHOW_TRIGGER_BRUSHES 62108 #define CAMWND_MENU_CMD_CUBIC_CLIPPING 62109 #define CAMWND_MENU_CMD_DECAL_EDITING 62110 +#define CAMWND_MENU_CMD_DECAL_SCALE_UP 62111 +#define CAMWND_MENU_CMD_DECAL_SCALE_DOWN 62112 +#define CAMWND_MENU_CMD_DECAL_ROTATE_CCW 62113 +#define CAMWND_MENU_CMD_DECAL_ROTATE_CW 62114 #define CAMWND_FILTER_HIDE_WORLD 0x00000001 #define CAMWND_FILTER_HIDE_PATCHES 0x00000002 @@ -1232,6 +1257,8 @@ static void CamWnd_ToggleRealtimeLighting(CCamWnd* cam) { CamWnd_SetRealtimeLighting(cam, !CamWnd_RealtimeLightingEnabled(cam)); } +static void CamWnd_ClearDecalPreview(CCamWnd* cam); + static bool CamWnd_DecalEditingEnabled(CCamWnd* cam) { if (!cam || !cam->GetSafeHwnd()) { return false; @@ -1243,6 +1270,9 @@ static void CamWnd_SetDecalEditing(CCamWnd* cam, bool enabled) { if (!cam || !cam->GetSafeHwnd()) { return; } + if (!enabled) { + CamWnd_ClearDecalPreview(cam); + } CamWnd_SetIntProp(cam->GetSafeHwnd(), CAMWND_PROP_DECAL_EDITING, enabled ? 1 : 0); CamWnd_RequestRedraw(cam); } @@ -1251,6 +1281,74 @@ static void CamWnd_ToggleDecalEditing(CCamWnd* cam) { CamWnd_SetDecalEditing(cam, !CamWnd_DecalEditingEnabled(cam)); } +static void CamWnd_ClearDecalPreview(CCamWnd* cam = NULL) { + const bool wasValid = s_camWndDecalPreviewValid; + if (!cam || s_camWndDecalPreviewOwner == cam->GetSafeHwnd()) { + s_camWndDecalPreviewValid = false; + s_camWndDecalPreviewOwner = NULL; + if (wasValid && cam && cam->GetSafeHwnd()) { + cam->InvalidateRect(NULL, FALSE); + } + } +} + +static void CamWnd_ScaleSelectedDecal(CCamWnd* cam, float scale) { + if (Map_ScaleSelectedDecal(scale)) { + CamWnd_RequestRedraw(cam); + } +} + +static void CamWnd_RotateStencil(CCamWnd* cam, float degrees) { + if (Map_GetSelectedDecal() >= 0) { + if (Map_RotateSelectedDecal(degrees)) { + CamWnd_RequestRedraw(cam); + } + return; + } + + s_camWndDecalPreviewRotation += degrees; + if (s_camWndDecalPreviewRotation >= 360.0f || s_camWndDecalPreviewRotation <= -360.0f) { + s_camWndDecalPreviewRotation = fmod(s_camWndDecalPreviewRotation, 360.0f); + } + CamWnd_ClearDecalPreview(cam); + if (cam && cam->GetSafeHwnd()) { + CamWnd_RequestRedraw(cam); + } +} + +static bool CamWnd_HandleDecalTransformChar(CCamWnd* cam, UINT nChar) { + if (!CamWnd_DecalEditingEnabled(cam)) { + return false; + } + + switch (nChar) { + case '+': + case '=': + if (Map_GetSelectedDecal() >= 0) { + CamWnd_ScaleSelectedDecal(cam, 1.25f); + return true; + } + break; + case '-': + case '_': + if (Map_GetSelectedDecal() >= 0) { + CamWnd_ScaleSelectedDecal(cam, 0.8f); + return true; + } + break; + case 'q': + case 'Q': + CamWnd_RotateStencil(cam, -15.0f); + return true; + case 'e': + case 'E': + CamWnd_RotateStencil(cam, 15.0f); + return true; + } + + return false; +} + static void CamWnd_ToggleFilterBit(CCamWnd* cam, int bit) { int mask = CamWnd_GetFilterMask(cam); mask ^= bit; @@ -1286,6 +1384,18 @@ static void CamWnd_HandleMenuCommand(CCamWnd* cam, int command) { case CAMWND_MENU_CMD_DECAL_EDITING: CamWnd_ToggleDecalEditing(cam); break; + case CAMWND_MENU_CMD_DECAL_SCALE_UP: + CamWnd_ScaleSelectedDecal(cam, 1.25f); + break; + case CAMWND_MENU_CMD_DECAL_SCALE_DOWN: + CamWnd_ScaleSelectedDecal(cam, 0.8f); + break; + case CAMWND_MENU_CMD_DECAL_ROTATE_CCW: + CamWnd_RotateStencil(cam, -15.0f); + break; + case CAMWND_MENU_CMD_DECAL_ROTATE_CW: + CamWnd_RotateStencil(cam, 15.0f); + break; case CAMWND_MENU_CMD_SHOW_WORLD: CamWnd_ToggleFilterBit(cam, CAMWND_FILTER_HIDE_WORLD); break; @@ -1356,6 +1466,14 @@ static void CamWnd_ShowDecalsMenu(HWND hWnd, CCamWnd* cam) { menu = CreatePopupMenu(); AppendMenu(menu, MF_STRING | (CamWnd_DecalEditingEnabled(cam) ? MF_CHECKED : 0), CAMWND_MENU_CMD_DECAL_EDITING, "Enable decal editing"); + AppendMenu(menu, MF_SEPARATOR, 0, NULL); + UINT scaleFlags = MF_STRING | (Map_GetSelectedDecal() >= 0 ? 0 : MF_GRAYED); + AppendMenu(menu, scaleFlags, CAMWND_MENU_CMD_DECAL_SCALE_UP, "Scale selected up (+)"); + AppendMenu(menu, scaleFlags, CAMWND_MENU_CMD_DECAL_SCALE_DOWN, "Scale selected down (-)"); + AppendMenu(menu, MF_SEPARATOR, 0, NULL); + UINT rotateFlags = MF_STRING | (CamWnd_DecalEditingEnabled(cam) ? 0 : MF_GRAYED); + AppendMenu(menu, rotateFlags, CAMWND_MENU_CMD_DECAL_ROTATE_CCW, "Rotate stencil left (Q)"); + AppendMenu(menu, rotateFlags, CAMWND_MENU_CMD_DECAL_ROTATE_CW, "Rotate stencil right (E)"); command = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, hWnd, NULL); DestroyMenu(menu); @@ -1816,29 +1934,24 @@ static void CamWnd_FaceExtrudeBuildRay(CCamWnd* cam, const CPoint& point, idVec3 dir.Normalize(); } -static bool CamWnd_DecalClick(CCamWnd* cam, const CPoint& point, UINT nFlags) { - if (!CamWnd_DecalEditingEnabled(cam) || cam == NULL) { +static bool CamWnd_BuildDecalAtPoint(CCamWnd* cam, const CPoint& point, renderWorldDecal_t& decal, const char** hitSource) { + if (cam == NULL) { return false; } idVec3 dir; CamWnd_FaceExtrudeBuildRay(cam, point, dir); - if (nFlags & MK_SHIFT) { - Map_SelectDecalByRay(cam->Camera().origin, dir); - return true; - } - idVec3 hitPoint; idVec3 hitNormal; float hitDist = HUGE_DISTANCE; bool hitBrush = false; - const char* hitSource = "none"; + const char* localHitSource = "none"; qertrace_t trace = Test_Ray(cam->Camera().origin, dir, 0); if (trace.brush != NULL && trace.face != NULL && trace.dist > 0.0f && trace.dist < hitDist) { hitBrush = true; - hitSource = "brush"; + localHitSource = "brush"; hitDist = trace.dist; hitPoint = cam->Camera().origin + dir * trace.dist; hitNormal = trace.face->plane.Normal(); @@ -1850,7 +1963,7 @@ static bool CamWnd_DecalClick(CCamWnd* cam, const CPoint& point, UINT nFlags) { if (g_qeglobals.rw->Trace(modelTrace, cam->Camera().origin, end, 0.0f, false, false)) { float modelDist = (modelTrace.point - cam->Camera().origin) * dir; if (!hitBrush && modelDist > 0.0f && modelDist < hitDist) { - hitSource = "render"; + localHitSource = "render"; hitDist = modelDist; hitPoint = modelTrace.point; hitNormal = modelTrace.normal; @@ -1859,11 +1972,12 @@ static bool CamWnd_DecalClick(CCamWnd* cam, const CPoint& point, UINT nFlags) { } if (hitDist <= 0.0f || hitDist >= HUGE_DISTANCE || hitNormal.LengthSqr() < 0.0001f) { - return true; + return false; } - renderWorldDecal_t decal; - CamWnd_DecalMaterial(decal.material); + if (!CamWnd_DecalMaterial(decal.material)) { + return false; + } CamWnd_DecalColorFromSelectedMaterial(decal.material, decal.color); decal.origin = hitPoint; decal.normal = hitNormal; @@ -1875,9 +1989,147 @@ static bool CamWnd_DecalClick(CCamWnd* cam, const CPoint& point, UINT nFlags) { decal.axisU = decal.normal.Cross(cam->Camera().vright); } decal.axisV = decal.normal.Cross(decal.axisU); + CamWnd_RotateDecalBasis(decal, s_camWndDecalPreviewRotation); decal.size = CamWnd_DecalFloat("size", "64"); decal.depth = CamWnd_DecalFloat("depth", "8"); decal.opacity = CamWnd_DecalFloat("opacity", "1"); + if (hitSource) { + *hitSource = localHitSource; + } + return true; +} + +static void CamWnd_UpdateDecalPreview(CCamWnd* cam, const CPoint& point) { + if (!CamWnd_DecalEditingEnabled(cam) || cam == NULL || !cam->GetSafeHwnd()) { + CamWnd_ClearDecalPreview(cam); + return; + } + if (Map_GetSelectedDecal() >= 0) { + CamWnd_ClearDecalPreview(cam); + return; + } + + renderWorldDecal_t preview; + if (!CamWnd_BuildDecalAtPoint(cam, point, preview, NULL)) { + CamWnd_ClearDecalPreview(cam); + return; + } + + s_camWndDecalPreview = preview; + s_camWndDecalPreviewOwner = cam->GetSafeHwnd(); + s_camWndDecalPreviewValid = true; + cam->InvalidateRect(NULL, FALSE); +} + +static bool CamWnd_HasDecalPreview(CCamWnd* cam) { + return cam && cam->GetSafeHwnd() && s_camWndDecalPreviewValid && s_camWndDecalPreviewOwner == cam->GetSafeHwnd(); +} + +static void CamWnd_SubmitWorldDecalsWithPreview(CCamWnd* cam) { + if (!g_qeglobals.rw) { + return; + } + + if (!CamWnd_HasDecalPreview(cam)) { + g_qeglobals.rw->SubmitWorldDecalsToScreenSpace(); + return; + } + + const int previewIndex = g_qeglobals.rw->AddWorldDecal(s_camWndDecalPreview); + g_qeglobals.rw->SubmitWorldDecalsToScreenSpace(); + if (previewIndex >= 0) { + g_qeglobals.rw->RemoveWorldDecal(previewIndex); + } +} + +static void CamWnd_DecalCorners(const renderWorldDecal_t& decal, idVec3 corners[4]) { + const idVec3 axisU = decal.axisU * (decal.size * 0.5f); + const idVec3 axisV = decal.axisV * (decal.size * 0.5f); + corners[0] = decal.origin - axisU - axisV; + corners[1] = decal.origin + axisU - axisV; + corners[2] = decal.origin + axisU + axisV; + corners[3] = decal.origin - axisU + axisV; +} + +static void CamWnd_DrawDecalOutlineGL(const renderWorldDecal_t& decal, const idVec4& color, float lineWidth) { + idVec3 corners[4]; + CamWnd_DecalCorners(decal, corners); + + const GLboolean depthEnabled = glIsEnabled(GL_DEPTH_TEST); + glPushAttrib(GL_CURRENT_BIT); + glDisable(GL_TEXTURE_2D); + glDisable(GL_DEPTH_TEST); + globalImages->BindNull(); + glLineWidth(lineWidth); + glColor4f(color.x, color.y, color.z, color.w); + glBegin(GL_LINE_LOOP); + for (int i = 0; i < 4; ++i) { + glVertex3fv(corners[i].ToFloatPtr()); + } + glEnd(); + glBegin(GL_LINES); + glVertex3fv(decal.origin.ToFloatPtr()); + idVec3 normalEnd = decal.origin + decal.normal * idMath::ClampFloat(4.0f, 32.0f, decal.size * 0.2f); + glVertex3fv(normalEnd.ToFloatPtr()); + glEnd(); + glLineWidth(1.0f); + if (depthEnabled) { + glEnable(GL_DEPTH_TEST); + } + glPopAttrib(); +} + +static void CamWnd_DrawDecalSelectionOverlayGL(CCamWnd* cam) { + if (!cam || !g_qeglobals.rw || !CamWnd_DecalEditingEnabled(cam)) { + return; + } + + const int selected = Map_GetSelectedDecal(); + if (selected >= 0) { + const renderWorldDecal_t* decal = g_qeglobals.rw->GetWorldDecal(selected); + if (decal) { + CamWnd_DrawDecalOutlineGL(*decal, idVec4(1.0f, 0.85f, 0.05f, 1.0f), 3.0f); + } + } + if (CamWnd_HasDecalPreview(cam)) { + CamWnd_DrawDecalOutlineGL(s_camWndDecalPreview, idVec4(0.1f, 0.8f, 1.0f, 1.0f), 1.5f); + } +} + +static void CamWnd_DebugDecalOutline(const renderWorldDecal_t& decal, const idVec4& color) { + if (!g_qeglobals.rw) { + return; + } + + idVec3 corners[4]; + CamWnd_DecalCorners(decal, corners); + for (int i = 0; i < 4; ++i) { + g_qeglobals.rw->DebugLine(color, corners[i], corners[(i + 1) & 3], 0, false); + } + g_qeglobals.rw->DebugLine(color, decal.origin, decal.origin + decal.normal * idMath::ClampFloat(4.0f, 32.0f, decal.size * 0.2f), 0, false); +} + +static bool CamWnd_DecalClick(CCamWnd* cam, const CPoint& point, UINT nFlags) { + if (!CamWnd_DecalEditingEnabled(cam) || cam == NULL) { + return false; + } + + idVec3 dir; + CamWnd_FaceExtrudeBuildRay(cam, point, dir); + + if (nFlags & MK_SHIFT) { + const int selected = Map_SelectDecalByRay(cam->Camera().origin, dir); + if (selected >= 0) { + CamWnd_ClearDecalPreview(cam); + } + return true; + } + + renderWorldDecal_t decal; + const char* hitSource = "none"; + if (!CamWnd_BuildDecalAtPoint(cam, point, decal, &hitSource)) { + return true; + } Map_AddDecal(decal); Sys_Status(va("Placed decal using %s on %s at %.2f %.2f %.2f\n", decal.material.c_str(), hitSource, decal.origin.x, decal.origin.y, decal.origin.z), 0); @@ -2913,6 +3165,7 @@ CCamWnd::~CCamWnd() { BEGIN_MESSAGE_MAP(CCamWnd, CWnd) //{{AFX_MSG_MAP(CCamWnd) ON_WM_KEYDOWN() + ON_WM_CHAR() ON_WM_PAINT() ON_WM_DESTROY() ON_WM_CLOSE() @@ -3024,6 +3277,10 @@ void CCamWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { CamWnd_FaceExtrudeEnd(this, false); return; } + if (nChar == VK_ESCAPE && CamWnd_DecalEditingEnabled(this) && Map_GetSelectedDecal() >= 0) { + Map_ClearSelectedDecal(); + return; + } if (nChar == VK_DELETE && CamWnd_DecalEditingEnabled(this) && Map_GetSelectedDecal() >= 0) { Map_DeleteSelectedDecal(); return; @@ -3034,6 +3291,17 @@ void CCamWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { g_pParentWnd->HandleKey(nChar, nRepCnt, nFlags); } +/* + ======================================================================================================================= + ======================================================================================================================= + */ +void CCamWnd::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { + if (CamWnd_HandleDecalTransformChar(this, nChar)) { + return; + } + CWnd::OnChar(nChar, nRepCnt, nFlags); +} + brush_t* g_pSplitList = NULL; /* @@ -3075,6 +3343,7 @@ void CCamWnd::SetXYFriend(CXYWnd* pWnd) { void CCamWnd::OnDestroy() { CamWnd_FaceExtrudeEnd(this, false); CamWnd_GizmoEnd(this, false); + CamWnd_ClearDecalPreview(this); CamWnd_DestroyMenuBar(this); CWnd::OnDestroy(); } @@ -3097,6 +3366,10 @@ void CCamWnd::OnMouseMove(UINT nFlags, CPoint point) { CRect r; GetClientRect(r); + if (GetCapture() != this) { + CamWnd_UpdateDecalPreview(this, point); + } + if (CamWnd_FaceExtrudeMouseMove(this, point)) { m_ptLastCursor = point; return; @@ -4265,8 +4538,16 @@ void CCamWnd::Cam_Draw() { } CamWnd_DrawMoveSelectedBrushSides(brushFilter); - if (!renderMode && g_qeglobals.rw && CamWnd_DecalEditingEnabled(this)) { - g_qeglobals.rw->SubmitWorldDecalsToScreenSpace(); + if (!renderMode && g_qeglobals.rw) { + if (CamWnd_DecalEditingEnabled(this)) { + CamWnd_UpdateDecalPreview(this, m_ptLastCursor); + CamWnd_SubmitWorldDecalsWithPreview(this); + CamWnd_DrawDecalSelectionOverlayGL(this); + } + else { + CamWnd_ClearDecalPreview(this); + g_qeglobals.rw->SubmitWorldDecalsToScreenSpace(); + } } CamWnd_FaceExtrudeDrawPreview(this); CamWnd_GizmoDraw(this); @@ -5866,14 +6147,28 @@ void CCamWnd::Cam_Render() { } Cam_SetGlobalFogFromWorldspawn(refdef); + if (CamWnd_DecalEditingEnabled(this)) { + CamWnd_UpdateDecalPreview(this, m_ptLastCursor); + } + + int previewDecalIndex = -1; + if (g_qeglobals.rw && CamWnd_HasDecalPreview(this)) { + previewDecalIndex = g_qeglobals.rw->AddWorldDecal(s_camWndDecalPreview); + } + if (g_qeglobals.rw && CamWnd_DecalEditingEnabled(this)) { for (int i = 0; i < g_qeglobals.rw->NumWorldDecals(); ++i) { const renderWorldDecal_t* decal = g_qeglobals.rw->GetWorldDecal(i); if (!decal) { continue; } - idVec4 color = (i == Map_GetSelectedDecal()) ? idVec4(1.0f, 0.85f, 0.1f, 1.0f) : idVec4(0.1f, 0.8f, 1.0f, 1.0f); + const bool selected = (i == Map_GetSelectedDecal()); + const bool preview = (i == previewDecalIndex); + idVec4 color = selected ? idVec4(1.0f, 0.85f, 0.1f, 1.0f) : idVec4(0.1f, 0.8f, 1.0f, 1.0f); g_qeglobals.rw->DebugCircle(color, decal->origin, decal->normal, decal->size * 0.5f, 32, 0, false); + if (selected || preview) { + CamWnd_DebugDecalOutline(*decal, color); + } } } @@ -5884,6 +6179,10 @@ void CCamWnd::Cam_Render() { renderSystem->EndFrame(&frontEnd, &backEnd, false); //common->Printf( "front:%i back:%i\n", frontEnd, backEnd ); + if (g_qeglobals.rw && previewDecalIndex >= 0) { + g_qeglobals.rw->RemoveWorldDecal(previewDecalIndex); + } + //glPopAttrib(); //DrawEntityData(); diff --git a/neo/engine/tools/radiant/CamWnd.h b/neo/engine/tools/radiant/CamWnd.h index 68b2ee1a..7de52e45 100644 --- a/neo/engine/tools/radiant/CamWnd.h +++ b/neo/engine/tools/radiant/CamWnd.h @@ -180,6 +180,7 @@ protected: void OriginalMouseUp(UINT nFlags, CPoint point); //{{AFX_MSG(CCamWnd) afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); + afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnPaint(); afx_msg void OnDestroy(); afx_msg void OnClose(); diff --git a/neo/engine/tools/radiant/EditorMap.cpp b/neo/engine/tools/radiant/EditorMap.cpp index 3620ebaa..c920f851 100644 --- a/neo/engine/tools/radiant/EditorMap.cpp +++ b/neo/engine/tools/radiant/EditorMap.cpp @@ -132,6 +132,15 @@ int Map_GetSelectedDecal(void) { return s_selectedWorldDecal; } +void Map_ClearSelectedDecal(void) { + if (s_selectedWorldDecal < 0) { + return; + } + s_selectedWorldDecal = -1; + Sys_UpdateWindows(W_CAMERA); + Sys_Status("Deselected decal\n", 0); +} + void Map_DeleteSelectedDecal(void) { if (!g_qeglobals.rw || s_selectedWorldDecal < 0) { return; @@ -142,6 +151,64 @@ void Map_DeleteSelectedDecal(void) { Sys_UpdateWindows(W_ALL); } +bool Map_ScaleSelectedDecal(float scale) { + if (!g_qeglobals.rw || s_selectedWorldDecal < 0 || scale <= 0.0f) { + return false; + } + + const renderWorldDecal_t* existing = g_qeglobals.rw->GetWorldDecal(s_selectedWorldDecal); + if (!existing) { + return false; + } + + renderWorldDecal_t decal = *existing; + decal.size *= scale; + if (decal.size < 1.0f) { + decal.size = 1.0f; + } + else if (decal.size > 4096.0f) { + decal.size = 4096.0f; + } + + if (!g_qeglobals.rw->UpdateWorldDecal(s_selectedWorldDecal, decal)) { + return false; + } + + mapModified = 1; + Sys_UpdateWindows(W_CAMERA); + Sys_Status(va("Scaled decal to %.2f\n", decal.size), 0); + return true; +} + +bool Map_RotateSelectedDecal(float degrees) { + if (!g_qeglobals.rw || s_selectedWorldDecal < 0) { + return false; + } + + const renderWorldDecal_t* existing = g_qeglobals.rw->GetWorldDecal(s_selectedWorldDecal); + if (!existing) { + return false; + } + + renderWorldDecal_t decal = *existing; + const float radians = degrees * idMath::M_DEG2RAD; + const float s = sin(radians); + const float c = cos(radians); + const idVec3 oldU = decal.axisU; + const idVec3 oldV = decal.axisV; + decal.axisU = oldU * c + oldV * s; + decal.axisV = oldV * c - oldU * s; + + if (!g_qeglobals.rw->UpdateWorldDecal(s_selectedWorldDecal, decal)) { + return false; + } + + mapModified = 1; + Sys_UpdateWindows(W_CAMERA); + Sys_Status(va("Rotated decal %.1f degrees\n", degrees), 0); + return true; +} + int Map_SelectDecalByRay(const idVec3& origin, const idVec3& dir) { if (!g_qeglobals.rw) { return -1; @@ -154,25 +221,43 @@ int Map_SelectDecalByRay(const idVec3& origin, const idVec3& dir) { if (!decal) { continue; } + + const float halfSize = decal->size * 0.5f; + const float tolerance = idMath::ClampFloat(4.0f, 24.0f, decal->size * 0.125f); + const float depthTolerance = (decal->depth > 1.0f ? decal->depth : 1.0f) + tolerance; float denom = dir * decal->normal; - if (fabs(denom) < 0.0001f) { - continue; + if (fabs(denom) >= 0.0001f) { + float t = ((decal->origin - origin) * decal->normal) / denom; + if (t > 0.0f && t < bestT) { + idVec3 hit = origin + dir * t; + idVec3 rel = hit - decal->origin; + float u = rel * decal->axisU; + float v = rel * decal->axisV; + if (fabs(u) <= halfSize + tolerance && fabs(v) <= halfSize + tolerance) { + bestT = t; + bestIndex = i; + } + } } - float t = ((decal->origin - origin) * decal->normal) / denom; - if (t <= 0.0f || t >= bestT) { - continue; - } - idVec3 hit = origin + dir * t; - idVec3 rel = hit - decal->origin; - float u = rel * decal->axisU; - float v = rel * decal->axisV; - if (fabs(u) <= decal->size * 0.5f && fabs(v) <= decal->size * 0.5f) { - bestT = t; - bestIndex = i; + + float centerT = (decal->origin - origin) * dir; + if (centerT > 0.0f && centerT < bestT) { + idVec3 closest = origin + dir * centerT; + idVec3 rel = closest - decal->origin; + float planeDist = fabs(rel * decal->normal); + float u = rel * decal->axisU; + float v = rel * decal->axisV; + if (planeDist <= depthTolerance && fabs(u) <= halfSize + tolerance && fabs(v) <= halfSize + tolerance) { + bestT = centerT; + bestIndex = i; + } } } s_selectedWorldDecal = bestIndex; Sys_UpdateWindows(W_CAMERA); + if (bestIndex >= 0) { + Sys_Status(va("Selected decal %i\n", bestIndex), 0); + } return bestIndex; } diff --git a/neo/engine/tools/radiant/EditorMap.h b/neo/engine/tools/radiant/EditorMap.h index dd5e8d47..2a2dc381 100644 --- a/neo/engine/tools/radiant/EditorMap.h +++ b/neo/engine/tools/radiant/EditorMap.h @@ -61,6 +61,9 @@ void Map_LoadDecals(const char *filename); void Map_SaveDecals(const char *filename); int Map_AddDecal(const renderWorldDecal_t& decal); void Map_DeleteSelectedDecal(void); +bool Map_ScaleSelectedDecal(float scale); +bool Map_RotateSelectedDecal(float degrees); +void Map_ClearSelectedDecal(void); int Map_SelectDecalByRay(const idVec3& origin, const idVec3& dir); int Map_GetSelectedDecal(void);