-
Notifications
You must be signed in to change notification settings - Fork 29
Add radial transparency and background image support #429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Radial Transparency System: - Implement radial transparency zones for upper map layers - Focused layer remains fully opaque, upper layers fade with distance - Configurable transparency amount (0.0 = only focused layer, 1.0 = max transparency) - Toggle radial transparency on/off - Hotkey support for quick toggle - Improves visibility of multi-level dungeons and vertical map sections Background Image Support: - Load custom background images for the map canvas - Multiple Middle-earth themed backgrounds included: * Arda maps (MERP style) * Arrival scenes (Shire, Caras Galadhon, Rivendell) * Moria Gate - Fit modes: Fit, Fill, Stretch, Tile, Center, Focused - Adjustable opacity (0-100%) - Focused mode with scale and offset controls - Background rendering behind map geometry Graphics Configuration: - New transparency controls in Graphics preferences - Background image browser and configuration panel - Live preview of settings - Fit mode selection with visual feedback - Scale and offset sliders for fine-tuning Shader Enhancements: - Updated vertex and fragment shaders for transparency support - Alpha blending for layered rendering - Proper depth testing with transparency - Optimized shader uniforms Technical Implementation: - Enhanced OpenGL rendering pipeline - Background texture loading and management - Layer-based transparency calculations - Shader program updates for new rendering modes This creates a more immersive and visually appealing mapping experience with improved depth perception.
Reviewer's GuideImplements configurable radial transparency and background-image rendering for the map canvas, introduces texture-set & seasonal texture controls, adds a communications panel with its configuration, centralizes hotkey configuration, and extends shaders/OpenGL plumbing to support new per-layer transparency and time-of-day effects. Sequence diagram for map rendering with background image, radial transparency, and time-of-daysequenceDiagram
participant MW as MainWindow
participant MC as MapCanvas
participant GL as OpenGL
participant CFG as Configuration
participant BLD as MapBatchBuilder
participant SH as Shaders
MW->>BLD: generateMapDataFinisher(textures,font,map,timeOfDay,playerRoom,playerHasLight)
activate BLD
BLD-->>BLD: visitRooms(..., timeOfDay, playerRoom, playerHasLight)
BLD-->>BLD: build LayerMeshesIntermediate per layer
BLD-->>BLD: create InternalData(timeOfDay)
BLD-->>MW: SharedMapBatchFinisher
deactivate BLD
MW->>MC: paintGL()
activate MC
MC->>MC: actuallyPaintGL()
MC->>GL: renderBackgroundImage()
activate GL
MC->>CFG: read canvas.advanced (backgroundImagePath, fitMode, opacity)
alt useBackgroundImage is false or path empty
GL->>GL: clear(backgroundColor alpha=1)
else background image enabled
MC->>MC: loadBackgroundImageIfNeeded()
MC->>GL: renderColoredTexturedQuads(fullscreen quad, state.withTexture0(texId))
end
GL-->>MC: background done
deactivate GL
MC->>MC: paintMap()
MC->>MC: renderMapBatches()
loop for each layer thisLayer
MC->>MC: drawLayer(thisLayer,currentLayer)
MC->>SH: LayerMeshes.render(thisLayer,currentLayer,playerPos,isNight)
activate SH
SH-->>SH: set GLRenderState.uniforms
SH-->>SH: set uPlayerPos,uCurrentLayer
SH-->>SH: set uEnableRadial, uIsNight
SH-->>GL: draw terrain, walls, overlays with blending
deactivate SH
end
MC-->>MW: frame complete
deactivate MC
Class diagram for updated configuration and graphics settingsclassDiagram
class Configuration {
+GeneralSettings general
+CanvasSettings canvas
+Hotkeys hotkeys
+CommsSettings comms
+MumeClockSettings mumeClock
}
class CanvasSettings {
+bool trilinearFiltering
+bool softwareOpenGL
+QString resourcesDirectory
+TextureSetEnum textureSet
+bool enableSeasonalTextures
+float layerTransparency
+bool enableRadialTransparency
+Advanced advanced
+VisibilityFilter visibilityFilter
}
class Advanced {
+NamedConfig~bool~ autoTilt
+NamedConfig~bool~ printPerfStats
+bool useBackgroundImage
+QString backgroundImagePath
+int backgroundFitMode
+float backgroundOpacity
+float backgroundFocusedScale
+float backgroundFocusedOffsetX
+float backgroundFocusedOffsetY
+FixedPoint~1~ fov
+FixedPoint~1~ verticalAngle
+FixedPoint~1~ horizontalAngle
+FixedPoint~1~ layerHeight
+Advanced()
+void registerChangeCallback(Lifetime lifetime, Function callback)
}
class VisibilityFilter {
+NamedConfig~bool~ generic
+NamedConfig~bool~ herb
+NamedConfig~bool~ river
+NamedConfig~bool~ place
+NamedConfig~bool~ mob
+NamedConfig~bool~ comment
+NamedConfig~bool~ road
+NamedConfig~bool~ object
+NamedConfig~bool~ action
+NamedConfig~bool~ locality
+NamedConfig~bool~ connections
+VisibilityFilter()
+bool isVisible(InfomarkClassEnum markerClass)
+void setVisible(InfomarkClassEnum markerClass, bool visible)
+bool isConnectionsVisible()
+void setConnectionsVisible(bool visible)
+void showAll()
+void hideAll()
+void registerChangeCallback(Lifetime lifetime, Function callback)
}
class Hotkeys {
+NamedConfig~QString~ fileOpen
+NamedConfig~QString~ fileSave
+NamedConfig~QString~ fileReload
+NamedConfig~QString~ fileQuit
+NamedConfig~QString~ editUndo
+NamedConfig~QString~ editRedo
+NamedConfig~QString~ editPreferences
+NamedConfig~QString~ editPreferencesAlt
+NamedConfig~QString~ editFindRooms
+NamedConfig~QString~ editRoom
+NamedConfig~QString~ viewZoomIn
+NamedConfig~QString~ viewZoomOut
+NamedConfig~QString~ viewZoomReset
+NamedConfig~QString~ viewLayerUp
+NamedConfig~QString~ viewLayerDown
+NamedConfig~QString~ viewLayerReset
+NamedConfig~QString~ viewRadialTransparency
+NamedConfig~QString~ viewStatusBar
+NamedConfig~QString~ viewScrollBars
+NamedConfig~QString~ viewMenuBar
+NamedConfig~QString~ viewAlwaysOnTop
+NamedConfig~QString~ panelLog
+NamedConfig~QString~ panelClient
+NamedConfig~QString~ panelGroup
+NamedConfig~QString~ panelRoom
+NamedConfig~QString~ panelAdventure
+NamedConfig~QString~ panelDescription
+NamedConfig~QString~ panelComms
+NamedConfig~QString~ modeMoveMap
+NamedConfig~QString~ modeRaypick
+NamedConfig~QString~ modeSelectRooms
+NamedConfig~QString~ modeSelectMarkers
+NamedConfig~QString~ modeSelectConnection
+NamedConfig~QString~ modeCreateMarker
+NamedConfig~QString~ modeCreateRoom
+NamedConfig~QString~ modeCreateConnection
+NamedConfig~QString~ modeCreateOnewayConnection
+NamedConfig~QString~ roomCreate
+NamedConfig~QString~ roomMoveUp
+NamedConfig~QString~ roomMoveDown
+NamedConfig~QString~ roomMergeUp
+NamedConfig~QString~ roomMergeDown
+NamedConfig~QString~ roomDelete
+NamedConfig~QString~ roomConnectNeighbors
+NamedConfig~QString~ roomMoveToSelected
+NamedConfig~QString~ roomUpdateSelected
}
class CommsSettings {
+NamedConfig~QColor~ tellColor
+NamedConfig~QColor~ whisperColor
+NamedConfig~QColor~ groupColor
+NamedConfig~QColor~ askColor
+NamedConfig~QColor~ sayColor
+NamedConfig~QColor~ emoteColor
+NamedConfig~QColor~ socialColor
+NamedConfig~QColor~ yellColor
+NamedConfig~QColor~ narrateColor
+NamedConfig~QColor~ prayColor
+NamedConfig~QColor~ shoutColor
+NamedConfig~QColor~ singColor
+NamedConfig~QColor~ backgroundColor
+NamedConfig~QColor~ talkerYouColor
+NamedConfig~QColor~ talkerPlayerColor
+NamedConfig~QColor~ talkerNpcColor
+NamedConfig~QColor~ talkerAllyColor
+NamedConfig~QColor~ talkerNeutralColor
+NamedConfig~QColor~ talkerEnemyColor
+NamedConfig~bool~ yellAllCaps
+NamedConfig~bool~ whisperItalic
+NamedConfig~bool~ emoteItalic
+NamedConfig~bool~ showTimestamps
+NamedConfig~bool~ saveLogOnExit
+NamedConfig~QString~ logDirectory
+NamedConfig~bool~ muteDirectTab
+NamedConfig~bool~ muteLocalTab
+NamedConfig~bool~ muteGlobalTab
+void read(QSettings conf)
+void write(QSettings conf) const
}
class MumeClockSettings {
+int64_t startEpoch
+bool display
+NamedConfig~bool~ gmcpBroadcast
+NamedConfig~int~ gmcpBroadcastInterval
+MumeClockSettings()
+void read(QSettings conf)
+void write(QSettings conf) const
}
class BackgroundFitModeEnum {
<<enumeration>>
FIT
FILL
STRETCH
CENTER
TILE
FOCUSED
}
Configuration --> CanvasSettings : has
Configuration --> Hotkeys : has
Configuration --> CommsSettings : has
Configuration --> MumeClockSettings : has
CanvasSettings --> Advanced : has
CanvasSettings --> VisibilityFilter : has
Advanced --> BackgroundFitModeEnum : uses
CanvasSettings --> TextureSetEnum : uses
VisibilityFilter --> InfomarkClassEnum : uses
Class diagram for MainWindow, communications, visibility, and hotkeysclassDiagram
class MainWindow {
-QDockWidget* m_dockDialogLog
-QDockWidget* m_dockDialogClient
-QDockWidget* m_dockDialogGroup
-QDockWidget* m_dockDialogRoom
-QDockWidget* m_dockDialogAdventure
-QDockWidget* m_dockDialogDescription
-QDockWidget* m_dockDialogComms
-QDockWidget* m_dockDialogVisibleMarkers
-std::unique_ptr~GameObserver~ m_gameObserver
-AutoLogger* m_logger
-MumeClock* m_mumeClock
-CommsManager* m_commsManager
-CommsWidget* m_commsWidget
-VisibilityFilterWidget* m_visibilityFilterWidget
-QAction* radialTransparencyAct
-QAction* saveCommsLogAct
+MainWindow()
+void createActions()
+void setupMenuBar()
+void wireConnections()
+void closeEvent(QCloseEvent* event)
+void slot_onPreferences()
+void slot_alwaysOnTop()
+void slot_setRadialTransparency()
+void applyHotkeys()
+void registerGlobalShortcuts()
}
class CommsManager {
+CommsManager(QObject* parent)
+void slot_parseGmcpInput(GmcpMessage gmcp)
+void slot_parseRawGameText(QString text)
+signal sig_log(QString msg)
}
class CommsWidget {
+CommsWidget(CommsManager manager, AutoLogger logger, QWidget* parent)
+void slot_saveLog()
+void slot_saveLogOnExit()
+void slot_loadSettings()
}
class VisibilityFilterWidget {
+VisibilityFilterWidget(QWidget* parent)
+signal sig_visibilityChanged()
+signal sig_connectionsVisibilityChanged()
}
class MapWindow {
+MapCanvas* getCanvas()
+void slot_graphicsSettingsChanged()
}
class MapCanvas {
+void slot_reloadTextures()
+void infomarksChanged()
}
class ConfigDialog {
+signal sig_graphicsSettingsChanged()
+signal sig_textureSettingsChanged()
+signal sig_groupSettingsChanged()
+signal sig_commsSettingsChanged()
+signal sig_hotkeysChanged()
}
class GameObserver {
+signal sig2_sentToUserGmcp(GmcpMessage gmcp)
+signal sig2_rawGameText(QString text)
+signal sig2_connected()
}
class MumeClock {
+MumeClock(int64_t startEpoch, GameObserver observer, QObject* parent)
+MumeMoment getMumeMoment() const
+signal sig_log(QString msg)
+signal sig_seasonChanged(SeasonEnum season)
}
class AutoLogger {
+AutoLogger(QObject* parent)
}
MainWindow --> CommsManager : creates
MainWindow --> CommsWidget : creates
MainWindow --> VisibilityFilterWidget : creates
MainWindow --> MapWindow : has
MainWindow --> ConfigDialog : uses
MainWindow --> GameObserver : owns
MainWindow --> MumeClock : owns
MainWindow --> AutoLogger : owns
GameObserver --> CommsManager : signals to
CommsManager --> CommsWidget : used by
VisibilityFilterWidget --> MapCanvas : signals to
ConfigDialog --> MapWindow : signals to
ConfigDialog --> MapCanvas : textureSettingsChanged
ConfigDialog --> CommsWidget : commsSettingsChanged
ConfigDialog --> MainWindow : hotkeysChanged
Class diagram for MapCanvas rendering, batches, shaders, and radial transparencyclassDiagram
class MapCanvas {
-std::optional~QString~ m_backgroundImagePath
-std::shared_ptr~MMTexture~ m_backgroundTexture
-OpenGL m_opengl
-int m_currentLayer
-glm::vec2 m_scroll
-MapBatches m_batches
+void actuallyPaintGL()
+void paintMap()
+void renderMapBatches()
+void loadBackgroundImageIfNeeded()
+void renderBackgroundImage()
+OpenGL& getOpenGL()
}
class MapBatches {
+std::unordered_map~int, LayerMeshes~ batchedMeshes
+BatchedConnections connectionDrawerBuffers
+std::unordered_map~int, RoomNameBatchIntermediate~ roomNameBatches
+bool isNight
+PendingUpdateFlashState pendingUpdateFlashState
}
class LayerMeshesIntermediate {
+LayerMeshes getLayerMeshes(OpenGL gl) const
}
class LayerMeshes {
+void render(int thisLayer, int focusedLayer, glm::vec3 playerPos, bool isNight)
}
class InternalData {
+std::unordered_map~int, LayerMeshesIntermediate~ batchedMeshes
+BatchedConnections connectionDrawerBuffers
+std::unordered_map~int, RoomNameBatchIntermediate~ roomNameBatches
+MumeTimeEnum timeOfDay
+void virt_finish(MapBatches output, OpenGL gl, GLFont font) const
}
class GLRenderState {
+BlendModeEnum blend
+std::optional~DepthParams~ depth
+Uniforms uniforms
+GLRenderState withDepthFunction(DepthFunctionEnum f) const
+GLRenderState withBlend(BlendModeEnum mode) const
+GLRenderState withColor(Color c) const
+GLRenderState withTexture0(TextureId id) const
}
class Uniforms {
+Color color
+Textures textures
+std::optional~float~ pointSize
+glm::vec3 playerPos
+int currentLayer
+bool enableRadialTransparency
+bool texturesDisabled
+bool isNight
}
class OpenGL {
+void clear(Color c)
+void renderColoredQuads(vector~ColorVert~ verts, GLRenderState state)
+void renderColoredTexturedQuads(vector~ColoredTexVert~ verts, GLRenderState state)
+void setTextureLookup(TextureId id, shared_ptr~MMTexture~ tex)
}
class AbstractShaderProgram {
+void setUniform1iv(GLint location, GLsizei count, GLint* value)
+void setUniform1fv(GLint location, GLsizei count, GLfloat* value)
+void setUniform3fv(GLint location, GLsizei count, GLfloat* value)
+void setUniform4fv(GLint location, GLsizei count, GLfloat* value)
+void setUniform4iv(GLint location, GLsizei count, GLint* value)
+void setUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, GLfloat* value)
+void setViewport(char* name, Viewport viewport)
+void setVec3(char* name, glm::vec3 v)
+void setInt(char* name, int value)
}
class BackgroundFitModeEnum {
<<enumeration>>
FIT
FILL
STRETCH
CENTER
TILE
FOCUSED
}
class MumeTimeEnum {
<<enumeration>>
UNKNOWN
DAY
DUSK
NIGHT
}
MapCanvas --> MapBatches : uses
MapCanvas --> OpenGL : uses
MapCanvas --> BackgroundFitModeEnum : uses
MapBatches --> LayerMeshes : contains
InternalData --> MapBatches : fills
InternalData --> LayerMeshesIntermediate : contains
InternalData --> MumeTimeEnum : uses
LayerMeshesIntermediate --> LayerMeshes : builds
LayerMeshes --> GLRenderState : configures
GLRenderState --> Uniforms : has
OpenGL --> AbstractShaderProgram : uses
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- The three fragment shaders (plain/ucolor, tex/acolor, tex/ucolor) now duplicate identical noise/fractal/organicPattern logic and radial-transparency code; consider factoring these helpers into a shared GLSL include or macro block so that behavior changes only need to be made in one place and stay in sync.
- The radial-transparency and night-lighting shaders use a lot of hard‑coded magic numbers for distances, thresholds, and alpha values (e.g., 0.4/0.7/1.3, 99.9, 1.1, various baseAlpha tables); it would be easier to tune and reason about if these were consolidated into named constants or uniforms driven from C++ rather than scattered literals.
- applyHotkeys() logs every shortcut application with qDebug, which could become quite noisy when users tweak preferences; you may want to either compile-guard or rate-limit these logs, or only log when something actually changes.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The three fragment shaders (plain/ucolor, tex/acolor, tex/ucolor) now duplicate identical noise/fractal/organicPattern logic and radial-transparency code; consider factoring these helpers into a shared GLSL include or macro block so that behavior changes only need to be made in one place and stay in sync.
- The radial-transparency and night-lighting shaders use a lot of hard‑coded magic numbers for distances, thresholds, and alpha values (e.g., 0.4/0.7/1.3, 99.9, 1.1, various baseAlpha tables); it would be easier to tune and reason about if these were consolidated into named constants or uniforms driven from C++ rather than scattered literals.
- applyHotkeys() logs every shortcut application with qDebug, which could become quite noisy when users tweak preferences; you may want to either compile-guard or rate-limit these logs, or only log when something actually changes.
## Individual Comments
### Comment 1
<location> `src/configuration/configuration.cpp:56-65` </location>
<code_context>
}
}
+NODISCARD TextureSetEnum intToTextureSet(int value)
+{
+ switch (value) {
+ case 0:
+ return TextureSetEnum::CLASSIC;
+ case 1:
+ return TextureSetEnum::MODERN;
+ case 2:
+ return TextureSetEnum::CUSTOM;
+ default:
+ return TextureSetEnum::MODERN; // Default to Modern
+ }
+}
+
+NODISCARD int textureSetToInt(TextureSetEnum value)
+{
+ return static_cast<int>(value);
+}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Enum <-> int mapping may get out of sync with `TextureSetEnum` underlying values
`intToTextureSet` hardcodes 0/1/2 → CLASSIC/MODERN/CUSTOM, but `textureSetToInt` relies on `static_cast<int>` matching those exact underlying enum values and order. If `TextureSetEnum` is reordered or given explicit values, saved config/UI indices could map to the wrong texture set. To avoid this, either centralize the mapping (e.g., a `constexpr` table used in both directions) or make `textureSetToInt` explicitly map the known enum constants to their intended integers so the mapping stays symmetric and stable if the enum changes.
</issue_to_address>
### Comment 2
<location> `src/preferences/graphicspage.cpp:192-200` </location>
<code_context>
graphicsSettingsChanged();
}
+
+void GraphicsPage::slot_textureSetChanged(int index)
+{
+ auto &config = setConfig().canvas;
+ switch (index) {
+ case 0:
+ config.textureSet = TextureSetEnum::CLASSIC;
+ break;
+ case 1:
+ config.textureSet = TextureSetEnum::MODERN;
+ break;
+ case 2:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Texture-set selection logic duplicates `intToTextureSet`, which risks drift
This manual index→`TextureSetEnum` mapping duplicates `intToTextureSet` in `configuration.cpp`. If one is updated (new enum value, changed default) and the other isn’t, UI selection, stored config, and runtime behavior can diverge. Consider calling `intToTextureSet(index)` here (with appropriate bounds handling) instead of maintaining a separate switch.
Suggested implementation:
```cpp
void GraphicsPage::slot_textureSetChanged(int index)
{
auto &config = setConfig().canvas;
// Delegate index→enum mapping to shared helper to avoid duplicated logic.
config.textureSet = intToTextureSet(index);
graphicsSettingsChanged();
emit sig_textureSettingsChanged();
}
```
1. Ensure the declaration of `intToTextureSet(int)` is visible in this translation unit. If it is not already included transitively, add the appropriate header at the top of `graphicspage.cpp`, for example:
- `#include "configuration.h"` (or whichever header declares `intToTextureSet` and `TextureSetEnum` in your project).
2. If `intToTextureSet` does *not* perform bounds checking or defaulting internally, update its implementation in `configuration.cpp` to:
- Clamp or validate the integer index.
- Return the desired default (likely `TextureSetEnum::MODERN`) for out-of-range values.
This keeps all index→enum policy in one place instead of reintroducing a `switch` here.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| NODISCARD TextureSetEnum intToTextureSet(int value) | ||
| { | ||
| switch (value) { | ||
| case 0: | ||
| return TextureSetEnum::CLASSIC; | ||
| case 1: | ||
| return TextureSetEnum::MODERN; | ||
| case 2: | ||
| return TextureSetEnum::CUSTOM; | ||
| default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Enum <-> int mapping may get out of sync with TextureSetEnum underlying values
intToTextureSet hardcodes 0/1/2 → CLASSIC/MODERN/CUSTOM, but textureSetToInt relies on static_cast<int> matching those exact underlying enum values and order. If TextureSetEnum is reordered or given explicit values, saved config/UI indices could map to the wrong texture set. To avoid this, either centralize the mapping (e.g., a constexpr table used in both directions) or make textureSetToInt explicitly map the known enum constants to their intended integers so the mapping stays symmetric and stable if the enum changes.
| void GraphicsPage::slot_textureSetChanged(int index) | ||
| { | ||
| auto &config = setConfig().canvas; | ||
| switch (index) { | ||
| case 0: | ||
| config.textureSet = TextureSetEnum::CLASSIC; | ||
| break; | ||
| case 1: | ||
| config.textureSet = TextureSetEnum::MODERN; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Texture-set selection logic duplicates intToTextureSet, which risks drift
This manual index→TextureSetEnum mapping duplicates intToTextureSet in configuration.cpp. If one is updated (new enum value, changed default) and the other isn’t, UI selection, stored config, and runtime behavior can diverge. Consider calling intToTextureSet(index) here (with appropriate bounds handling) instead of maintaining a separate switch.
Suggested implementation:
void GraphicsPage::slot_textureSetChanged(int index)
{
auto &config = setConfig().canvas;
// Delegate index→enum mapping to shared helper to avoid duplicated logic.
config.textureSet = intToTextureSet(index);
graphicsSettingsChanged();
emit sig_textureSettingsChanged();
}
- Ensure the declaration of
intToTextureSet(int)is visible in this translation unit. If it is not already included transitively, add the appropriate header at the top ofgraphicspage.cpp, for example:#include "configuration.h"(or whichever header declaresintToTextureSetandTextureSetEnumin your project).
- If
intToTextureSetdoes not perform bounds checking or defaulting internally, update its implementation inconfiguration.cppto:- Clamp or validate the integer index.
- Return the desired default (likely
TextureSetEnum::MODERN) for out-of-range values.
This keeps all index→enum policy in one place instead of reintroducing aswitchhere.
Radial Transparency System:
Background Image Support:
Graphics Configuration:
Shader Enhancements:
Technical Implementation:
This creates a more immersive and visually appealing mapping experience with improved depth perception.
Summary by Sourcery
Introduce configurable radial transparency and background imagery for the map canvas, expand graphics and texture options, and add richer UI tooling for communications, hotkeys, visibility filtering, and seasonal rendering.
New Features:
Enhancements: