### Complete Plugin Director Example Source: https://context7.com/caspervg/sc4-render-services/llms.txt This C++ code demonstrates a complete plugin director for a framework, including service acquisition (ImGui and Terrain Decal services), panel registration, and proper cleanup during the plugin's lifecycle. It shows how to initialize and render a custom ImGui panel that interacts with the terrain decal service. ```cpp #include "cIGZFrameWork.h" #include "cRZCOMDllDirector.h" #include "imgui.h" #include "public/ImGuiPanelAdapter.h" #include "public/ImGuiServiceIds.h" #include "public/cIGZImGuiService.h" #include "public/cIGZTerrainDecalService.h" #include "public/TerrainDecalServiceIds.h" namespace { constexpr auto kMyDirectorID = 0xDEADBEEF; constexpr uint32_t kMyPanelId = 0x12345678; class MyPanel final : public ImGuiPanel { public: MyPanel(cIGZTerrainDecalService* decalService) : decalService_(decalService) {} void OnInit() override { // Initialize panel state } void OnRender() override { ImGui::Begin("My Plugin"); ImGui::Text("Decal count: %u", decalService_->GetDecalCount()); if (ImGui::Button("Create Decal")) { TerrainDecalState state{}; state.textureKey = cGZPersistResourceKey{0x7AB50E44, 0x1ABE787D, 0xAA40173A}; state.overlayType = cISTETerrainView::tOverlayManagerType::DynamicLand; state.decalInfo.center = cS3DVector2{512.0f, 512.0f}; state.decalInfo.baseSize = 16.0f; state.opacity = 1.0f; state.enabled = true; TerrainDecalId id{}; decalService_->CreateDecal(state, &id); } ImGui::End(); } void OnShutdown() override { delete this; } private: cIGZTerrainDecalService* decalService_; }; } class MyPluginDirector final : public cRZCOMDllDirector { public: uint32_t GetDirectorID() const override { return kMyDirectorID; } bool OnStart(cIGZCOM* pCOM) override { cRZCOMDllDirector::OnStart(pCOM); if (mpFrameWork) { mpFrameWork->AddHook(this); } return true; } bool PostAppInit() override { if (!mpFrameWork || panelRegistered_) return true; // Acquire ImGui service if (!mpFrameWork->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService, reinterpret_cast(&imguiService_))) { return true; } // Acquire Terrain Decal service if (!mpFrameWork->GetSystemService(kTerrainDecalServiceID, GZIID_cIGZTerrainDecalService, reinterpret_cast(&decalService_))) { imguiService_->Release(); imguiService_ = nullptr; return true; } // Create and register panel auto* panel = new MyPanel(decalService_); ImGuiPanelDesc desc = ImGuiPanelAdapter::MakeDesc(panel, kMyPanelId, 100, true); if (!imguiService_->RegisterPanel(desc)) { delete panel; decalService_->Release(); imguiService_->Release(); decalService_ = nullptr; imguiService_ = nullptr; return true; } panelRegistered_ = true; return true; } bool PostAppShutdown() override { if (imguiService_) { imguiService_->UnregisterPanel(kMyPanelId); imguiService_->Release(); imguiService_ = nullptr; } if (decalService_) { decalService_->Release(); decalService_ = nullptr; } panelRegistered_ = false; return true; } private: cIGZImGuiService* imguiService_ = nullptr; cIGZTerrainDecalService* decalService_ = nullptr; bool panelRegistered_ = false; }; static MyPluginDirector sDirector; cRZCOMDllDirector* RZGetCOMDllDirector() { static bool sAddedRef = false; if (!sAddedRef) { sDirector.AddRef(); sAddedRef = true; } return &sDirector; } ``` -------------------------------- ### Minimal Terrain Decal Service Usage Source: https://github.com/caspervg/sc4-render-services/blob/main/docs/services.md Demonstrates the basic steps to get the Terrain Decal Service, create a decal with specified properties, and then release the service. Ensure the service is available before proceeding. ```cpp cIGZTerrainDecalService* terrainDecalService = nullptr; if (!fw->GetSystemService(kTerrainDecalServiceID, GZIID_cIGZTerrainDecalService, reinterpret_cast(&terrainDecalService))) { return; } TerrainDecalState state{}; state.textureKey = cGZPersistResourceKey{0x7AB50E44, 0x1ABE787D, 0xAA40173A}; state.overlayType = cISTETerrainView::tOverlayManagerType::DynamicLand; state.decalInfo.center = cS3DVector2{512.0f, 512.0f}; state.decalInfo.baseSize = 16.0f; state.decalInfo.aspectMultiplier = 1.0f; state.decalInfo.uvScaleU = 1.0f; state.decalInfo.uvScaleV = 1.0f; state.opacity = 1.0f; state.enabled = true; TerrainDecalId id{}; terrainDecalService->CreateDecal(state, &id); terrainDecalService->Release(); ``` -------------------------------- ### Create a Terrain Decal Source: https://github.com/caspervg/sc4-render-services/blob/main/docs/terrain-decals.md Minimal example of creating a terrain decal. Requires a valid texture key, overlay type, and decal placement data. The creation can fail if there is no active city, invalid state, missing overlay manager, or version mismatch. ```cpp TerrainDecalState state{}; state.textureKey = cGZPersistResourceKey{ 0x7AB50E44, 0x1ABE787D, 0xAA40173A }; state.overlayType = cISTETerrainView::tOverlayManagerType::DynamicLand; state.decalInfo.center = cS3DVector2{512.0f, 512.0f}; state.decalInfo.baseSize = 16.0f; state.decalInfo.rotationTurns = 0.0f; state.decalInfo.aspectMultiplier = 1.0f; state.decalInfo.uvScaleU = 1.0f; state.decalInfo.uvScaleV = 1.0f; state.decalInfo.uvOffset = 0.0f; state.opacity = 1.0f; state.enabled = true; state.color = cS3DVector3(1.0f, 1.0f, 1.0f); TerrainDecalId id{}; if (!terrainDecalService->CreateDecal(state, &id)) { // no active city, invalid state, missing overlay manager, or version mismatch } ``` -------------------------------- ### Detect SimCity 4 Installation and Deploy Artifacts Source: https://github.com/caspervg/sc4-render-services/blob/main/CMakeLists.txt This CMake logic identifies the game installation path and defines post-build commands to deploy various project components to the game's Apps and Plugins folders. ```cmake if(WIN32 AND NOT DEFINED ENV{CI}) # Try to find SC4 installation directory set(SC4_INSTALL_PATHS "C:/Program Files (x86)/SimCity 4 Deluxe Edition" "C:/Program Files (x86)/Maxis/SimCity 4 Deluxe" "C:/Program Files/Maxis/SimCity 4 Deluxe" "C:/Program Files (x86)/Steam/steamapps/common/SimCity 4 Deluxe" "C:/Program Files/Steam/steamapps/common/SimCity 4 Deluxe" "C:/Program Files (x86)/GOG Galaxy/Games/SimCity 4 Deluxe Edition" "C:/GOG Games/SimCity 4 Deluxe Edition" ) foreach(SC4_PATH ${SC4_INSTALL_PATHS}) if(EXISTS "${SC4_PATH}/Apps/SimCity 4.exe") set(SC4_INSTALL_DIR "${SC4_PATH}") break() endif() endforeach() # Get user's Documents folder set(USERPROFILE_DIR "$ENV{USERPROFILE}") set(SC4_PLUGINS_DIR "${USERPROFILE_DIR}/Documents/SimCity 4/Plugins") if(SC4_INSTALL_DIR) message(STATUS "Found SC4 installation at: ${SC4_INSTALL_DIR}") add_custom_command(TARGET imgui POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_INSTALL_DIR}/Apps" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${SC4_INSTALL_DIR}/Apps/" COMMENT "Deploying imgui.dll to Apps folder" ) add_custom_command(TARGET SC4RenderServices POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_PLUGINS_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${SC4_PLUGINS_DIR}/" COMMAND ${CMAKE_COMMAND} -DSRC=${SC4RS_ROOT}/SC4RenderServices.ini -DDST=${SC4_PLUGINS_DIR}/SC4RenderServices.ini -P ${SC4RS_ROOT}/cmake/CopyIfNotExists.cmake COMMENT "Deploying SC4RenderServices.dll to Plugins folder (INI only if missing)" ) add_custom_command(TARGET SC4ImGuiSample POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_PLUGINS_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${SC4_PLUGINS_DIR}/" COMMENT "Deploying SC4ImGuiSample.dll to Plugins folder" ) add_custom_command(TARGET SC4ImGuiSampleCity POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_PLUGINS_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${SC4_PLUGINS_DIR}/" COMMENT "Deploying SC4ImGuiSampleCity.dll to Plugins folder" ) add_custom_command(TARGET SC4ImGuiSampleDemo POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_PLUGINS_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${SC4_PLUGINS_DIR}/" COMMENT "Deploying SC4ImGuiSampleDemo.dll to Plugins folder" ) add_custom_command(TARGET SC4ImGuiTextureSample POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_PLUGINS_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${SC4_PLUGINS_DIR}/" COMMENT "Deploying SC4ImGuiTextureSample.dll to Plugins folder" ) add_custom_command(TARGET SC4WorldProjectionSample POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_PLUGINS_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${SC4_PLUGINS_DIR}/" COMMENT "Deploying SC4WorldProjectionSample.dll to Plugins folder" ) add_custom_command(TARGET SC4DrawServiceSample POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_PLUGINS_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${SC4_PLUGINS_DIR}/" COMMENT "Deploying SC4DrawServiceSample.dll to Plugins folder" ) add_custom_command(TARGET SC4RoadDecalSample POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_PLUGINS_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${SC4_PLUGINS_DIR}/" COMMENT "Deploying SC4RoadDecalSample.dll to Plugins folder" ) add_custom_command(TARGET SC4TerrainDecalSample POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${SC4_PLUGINS_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ``` -------------------------------- ### Get and Use S3D Camera Service Source: https://github.com/caspervg/sc4-render-services/blob/main/docs/services.md Retrieves the S3D Camera Service and uses WorldToScreen to convert world coordinates to screen coordinates. Ensure the service is available before use. ```cpp cIGZS3DCameraService* cameraService = nullptr; if (!fw->GetSystemService(kS3DCameraServiceID, GZIID_cIGZS3DCameraService, reinterpret_cast(&cameraService))) { return; } auto handle = cameraService->WrapActiveRendererCamera(); float sx = 0.0f; float sy = 0.0f; if (cameraService->WorldToScreen(handle, worldX, worldY, worldZ, sx, sy)) { // use screen coordinates } cameraService->Release(); ``` -------------------------------- ### Acquiring Direct3D 7 Interfaces Source: https://github.com/caspervg/sc4-render-services/blob/main/README.md Demonstrates how to acquire raw Direct3D 7 interfaces for advanced use cases. Remember to Release() the interfaces when done. Use `IsDeviceReady()` and `GetDeviceGeneration()` to detect when cached resources need recreation. ```cpp IDirect3DDevice7* d3d = nullptr; IDirectDraw7* dd = nullptr; if (service->AcquireD3DInterfaces(&d3d, &dd)) { // use d3d/dd d3d->Release(); dd->Release(); } ``` -------------------------------- ### Configure Include Directories and Versioning Source: https://github.com/caspervg/sc4-render-services/blob/main/CMakeLists.txt Sets up include paths and parses the project version string into major, minor, patch, and build components for use in resource files. ```cmake include_directories( ${SC4RS_ROOT}/src ) set(SC4_COMMON_DIRECTOR_DEF ${SC4RS_ROOT}/SC4Director.def) set(SC4RS_VERSION_INPUT "${PROJECT_VERSION}") if(DEFINED SC4RS_RELEASE_VERSION AND NOT SC4RS_RELEASE_VERSION STREQUAL "") set(SC4RS_VERSION_INPUT "${SC4RS_RELEASE_VERSION}") endif() string(REGEX REPLACE "^v" "" SC4RS_PRODUCT_VERSION_STR "${SC4RS_VERSION_INPUT}") if(SC4RS_PRODUCT_VERSION_STR MATCHES "^([0-9]+)\.([0-9]+)\.([0-9]+)(\.([0-9]+))?([-.].*)?$") set(SC4RS_FILE_VERSION_MAJOR "${CMAKE_MATCH_1}") set(SC4RS_FILE_VERSION_MINOR "${CMAKE_MATCH_2}") set(SC4RS_FILE_VERSION_PATCH "${CMAKE_MATCH_3}") if(CMAKE_MATCH_5) set(SC4RS_FILE_VERSION_BUILD "${CMAKE_MATCH_5}") else() set(SC4RS_FILE_VERSION_BUILD 0) endif() else() message(FATAL_ERROR "SC4RS_RELEASE_VERSION must look like v1.2.3 or v1.2.3.4 (got '${SC4RS_VERSION_INPUT}')") endif() set(SC4RS_FILE_VERSION_COMMAS "${SC4RS_FILE_VERSION_MAJOR},${SC4RS_FILE_VERSION_MINOR},${SC4RS_FILE_VERSION_PATCH},${SC4RS_FILE_VERSION_BUILD}") set(SC4RS_FILE_VERSION_DOTS "${SC4RS_FILE_VERSION_MAJOR}.${SC4RS_FILE_VERSION_MINOR}.${SC4RS_FILE_VERSION_PATCH}.${SC4RS_FILE_VERSION_BUILD}") set(SC4_RENDER_SERVICES_VERSION_RESOURCE "") if(WIN32) configure_file( ${SC4RS_ROOT}/cmake/SC4RenderServicesVersion.rc.in ${CMAKE_CURRENT_BINARY_DIR}/SC4RenderServicesVersion.rc @ONLY ) set(SC4_RENDER_SERVICES_VERSION_RESOURCE ${CMAKE_CURRENT_BINARY_DIR}/SC4RenderServicesVersion.rc) endif() ``` -------------------------------- ### Basic ImGuiTexture Usage with RAII Wrapper Source: https://github.com/caspervg/sc4-render-services/blob/main/README.md Demonstrates the recommended RAII wrapper for ImGuiTexture. It automatically manages texture lifetime, including creation and release. Ensure pixel data is in RGBA32 format. Call from the render thread. ```cpp #include "public/ImGuiTexture.h" class MyPanel { cIGZImGuiService* service_; ImGuiTexture myTexture_; void OnInit() { // Generate RGBA32 pixel data (4 bytes per pixel) std::vector pixels(128 * 128 * 4); // ... fill pixel data ... // Create texture - automatically manages lifetime if (!myTexture_.Create(service_, 128, 128, pixels.data())) { // Handle error } } void OnRender() { // Get texture ID - returns nullptr if device was lost void* texId = myTexture_.GetID(); if (texId) { ImGui::Image(texId, ImVec2(128, 128)); } else { ImGui::TextUnformatted("Texture unavailable"); } } // Texture is automatically released in destructor }; ``` -------------------------------- ### Error Handling for Texture Operations Source: https://github.com/caspervg/sc4-render-services/blob/main/README.md Provides examples of checking return values for texture creation and retrieval. `GetTextureID` can return `nullptr` on device loss or generation mismatch. `IsTextureValid` helps distinguish stale handles. ```cpp // Creation can fail - check handle ImGuiTextureHandle handle = service->CreateTexture(desc); if (handle.id == 0) { LOG_ERROR("Texture creation failed"); return; } // GetTextureID returns nullptr on failure void* texId = service->GetTextureID(handle); if (!texId) { // Device lost, generation mismatch, or surface recreation failed // Check IsTextureValid() to distinguish: if (!service->IsTextureValid(handle)) { // Handle is stale (generation mismatch) // Need to create new texture } } ``` -------------------------------- ### Build the project using CMake Source: https://github.com/caspervg/sc4-render-services/blob/main/README.md Commands to configure and build the project from the repository root using the Visual Studio toolchain. ```bash cmake -S . -B cmake-build-debug-visual-studio -G "Visual Studio 17 2022" cmake --build cmake-build-debug-visual-studio --config Debug ``` -------------------------------- ### Register an ImGui panel Source: https://github.com/caspervg/sc4-render-services/blob/main/docs/services.md Define a render callback and register it with the ImGui service using a panel descriptor. Ensure the service is released after registration. ```cpp static void RenderPanel(void* data) { ImGui::Begin("My Panel"); ImGui::TextUnformatted("Hello from a client DLL."); ImGui::End(); } void RegisterPanel(cIGZFrameWork* fw) { cIGZImGuiService* service = nullptr; if (!fw->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService, reinterpret_cast(&service))) { return; } ImGuiPanelDesc desc{}; desc.id = 0x12345678; desc.order = 100; desc.visible = true; desc.on_render = &RenderPanel; service->RegisterPanel(desc); service->Release(); } ``` -------------------------------- ### Register ImGui Panel with Callbacks Source: https://context7.com/caspervg/sc4-render-services/llms.txt Registers an ImGui panel using a set of callback functions for initialization, rendering, and lifecycle management. Ensure the service manages the ImGui context; do not call ImGui::CreateContext() or ImGui::DestroyContext() directly. ```cpp #include "imgui.h" #include "public/cIGZImGuiService.h" #include "public/ImGuiServiceIds.h" static void OnPanelInit(void* data) { // Called once after ImGui initializes } static void OnPanelUpdate(void* data) { // Called every frame before on_render when visible } static void OnPanelRender(void* data) { ImGui::Begin("My Plugin Panel"); ImGui::TextUnformatted("Hello from my SimCity 4 plugin!"); static int counter = 0; if (ImGui::Button("Click Me")) { counter++; } ImGui::Text("Button clicked %d times", counter); ImGui::End(); } static void OnPanelShutdown(void* data) { // Cleanup when service shuts down } void RegisterMyPanel(cIGZFrameWork* fw) { cIGZImGuiService* service = nullptr; if (!fw->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService, reinterpret_cast(&service))) { return; } ImGuiPanelDesc desc{}; desc.id = 0x12345678; // Unique panel ID desc.order = 100; // Render order (lower = first) desc.visible = true; // Initial visibility desc.on_init = &OnPanelInit; desc.on_update = &OnPanelUpdate; desc.on_render = &OnPanelRender; desc.on_shutdown = &OnPanelShutdown; desc.on_visible_changed = nullptr; desc.on_unregister = nullptr; desc.on_device_lost = nullptr; desc.on_device_restored = nullptr; desc.data = nullptr; // User data passed to callbacks desc.fontId = 0; // 0 = default font if (!service->RegisterPanel(desc)) { // Handle registration failure } service->Release(); } ``` -------------------------------- ### Register ImGui Panel with Class-Based Adapter Source: https://context7.com/caspervg/sc4-render-services/llms.txt Provides an object-oriented approach to panel creation using the ImGuiPanel base class and ImGuiPanelAdapter. This pattern encapsulates panel state within a class and automatically wires virtual methods to the panel descriptor callbacks. ```cpp #include "imgui.h" #include "public/cIGZImGuiService.h" #include "public/ImGuiPanelAdapter.h" #include "public/ImGuiServiceIds.h" class MyPluginPanel final : public ImGuiPanel { public: void OnInit() override { // Initialize panel state clickCount_ = 0; } void OnUpdate() override { // Update logic each frame frameCount_++; } void OnRender() override { ImGui::Begin("My Plugin", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::Text("Frame: %d", frameCount_); ImGui::Checkbox("Show Details", &showDetails_); if (showDetails_) { ImGui::Separator(); ImGui::TextUnformatted("This panel uses the class-based adapter."); } if (ImGui::Button("Click")) { clickCount_++; } ImGui::SameLine(); ImGui::Text("Clicks: %d", clickCount_); ImGui::End(); } void OnVisibleChanged(bool visible) override { // Handle visibility changes } void OnShutdown() override { // Cleanup - delete self delete this; } void OnUnregister() override { // Called when explicitly unregistered } private: bool showDetails_ = true; int clickCount_ = 0; int frameCount_ = 0; }; void RegisterClassPanel(cIGZFrameWork* fw) { cIGZImGuiService* service = nullptr; if (!fw->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService, reinterpret_cast(&service))) { return; } auto* panel = new MyPluginPanel(); ImGuiPanelDesc desc = ImGuiPanelAdapter::MakeDesc( panel, // Panel instance 0xABCD1234, // Unique panel ID 100, // Render order true // Initially visible ); if (!service->RegisterPanel(desc)) { delete panel; } service->Release(); } ``` -------------------------------- ### Define ImGui Sample Libraries Source: https://github.com/caspervg/sc4-render-services/blob/main/CMakeLists.txt Defines shared libraries for ImGui samples, including source files and build properties. ```cmake set(IMGUI_SAMPLE_SOURCES ${SC4RS_ROOT}/src/sample/ImGuiSampleDirector.cpp ${SC4RS_ROOT}/src/utils/Logger.cpp ) add_library(SC4ImGuiSample SHARED ${IMGUI_SAMPLE_SOURCES} ${SC4_COMMON_DIRECTOR_DEF}) target_include_directories(SC4ImGuiSample PRIVATE ${SC4RS_ROOT}/src ${SC4RS_ROOT}/src/service ${IMGUI_DIR} ) target_link_libraries(SC4ImGuiSample PRIVATE imgui gzcom2 spdlog ) target_compile_definitions(SC4ImGuiSample PRIVATE IMGUI_DLL_IMPORT) set_target_properties(SC4ImGuiSample PROPERTIES OUTPUT_NAME "SC4ImGuiSample" SUFFIX ".dll" PREFIX "" ) ``` ```cmake set(IMGUI_SAMPLE_CITY_SOURCES ${SC4RS_ROOT}/src/sample/ImGuiSampleCityDirector.cpp ${SC4RS_ROOT}/src/utils/Logger.cpp ) add_library(SC4ImGuiSampleCity SHARED ${IMGUI_SAMPLE_CITY_SOURCES} ${SC4_COMMON_DIRECTOR_DEF}) target_include_directories(SC4ImGuiSampleCity PRIVATE ${SC4RS_ROOT}/src ${SC4RS_ROOT}/src/service ${IMGUI_DIR} ) target_link_libraries(SC4ImGuiSampleCity PRIVATE imgui gzcom2 spdlog ) target_compile_definitions(SC4ImGuiSampleCity PRIVATE IMGUI_DLL_IMPORT) set_target_properties(SC4ImGuiSampleCity PROPERTIES OUTPUT_NAME "SC4ImGuiSampleCity" SUFFIX ".dll" PREFIX "" ) ``` -------------------------------- ### Acquire a service via cIGZFrameWork Source: https://github.com/caspervg/sc4-render-services/blob/main/docs/services.md Use the service ID and IID to retrieve a service interface. Callers are responsible for calling Release() on the returned pointer. ```cpp cIGZImGuiService* service = nullptr; if (fw->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService, reinterpret_cast(&service))) { // use service service->Release(); } ``` -------------------------------- ### Configure SC4RenderServices.ini Source: https://github.com/caspervg/sc4-render-services/blob/main/README.md Configuration file settings for logging, UI appearance, and service enablement. Place this file in the Plugins folder next to the DLL. ```ini [SC4RenderServices] ; Logging verbosity. ; Valid values: trace, debug, info, warn, error, critical, off LogLevel=info ; Write logs to file. Set to false for MSVC debug output only. LogToFile=true ; ImGui base font size in pixels. Valid range: 8.0 - 32.0 FontSize=13.0 ; Custom .ttf font file path (relative to DLL folder). ; Leave empty to use the built-in ProggyVector font. FontFile= ; Font oversampling level (1-3). Higher = crisper text, more memory. FontOversample=2 ; ImGui color theme. Valid values: dark, light, classic Theme=dark ; Enable ImGui keyboard navigation. KeyboardNav=true ; Global UI scale factor. Valid range: 0.5 - 3.0 UIScale=1.0 ; Show a built-in status panel and the Dear ImGui demo window on startup. ; Useful for verifying that the service installed correctly. ShowDemoPanel=false ; Enable or disable individual services. EnableImGuiService=true EnableS3DCameraService=true EnableDrawService=true EnableTerrainDecalService=true ; Enables the custom terrain decal renderer path used for UV subrect support ; and clipped decal rendering. EnableTerrainDecalExperimentalRenderer=true ``` -------------------------------- ### ImGui Library Integration Source: https://github.com/caspervg/sc4-render-services/blob/main/CMakeLists.txt Manually configures the ImGui library by gathering source files and linking necessary Windows system libraries. ```cmake # ImGui library (vendor/imguid3d7 has no CMakeLists) set(IMGUI_DIR ${SC4RS_ROOT}/vendor/d3d7imgui/ImGui) # Add all core ImGui .cpp and .h files file(GLOB IMGUI_CORE_SOURCES ${IMGUI_DIR}/*.cpp ${IMGUI_DIR}/*.h ) # Explicitly add the Win32 and DX7 backend files set(IMGUI_BACKENDS ${IMGUI_DIR}/imgui_impl_win32.h ${IMGUI_DIR}/imgui_impl_win32.cpp ${IMGUI_DIR}/imgui_impl_dx7.h ${IMGUI_DIR}/imgui_impl_dx7.cpp ) add_library(imgui SHARED ${IMGUI_CORE_SOURCES} ${IMGUI_BACKENDS}) target_include_directories(imgui PUBLIC ${IMGUI_DIR} ) target_compile_definitions(imgui PRIVATE IMGUI_DLL_EXPORT) if(WIN32) target_link_libraries(imgui PRIVATE user32 gdi32 ddraw dxguid) endif() # Preserve existing link name in this project while using the vendor CMake target. add_library(gzcom2 ALIAS gzcom_dll) ``` -------------------------------- ### RAII Texture Management with ImGuiTexture Source: https://context7.com/caspervg/sc4-render-services/llms.txt Use the ImGuiTexture RAII wrapper for automatic texture creation, device-loss handling, and cleanup. It simplifies texture management by surviving Alt+Tab and resolution changes. ```cpp #include "public/ImGuiTexture.h" #include "public/cIGZImGuiService.h" #include class TexturedPanel final : public ImGuiPanel { public വല: explicit TexturedPanel(cIGZImGuiService* service) : service_(service) {} void OnInit() override { // Generate 64x64 checkerboard pattern (RGBA32 format) const int size = 64; std::vector pixels(size * size * 4); for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { int idx = (y * size + x) * 4; bool isWhite = ((x / 8) + (y / 8)) % 2 == 0; uint8_t color = isWhite ? 255 : 64; pixels[idx + 0] = color; // R pixels[idx + 1] = color; // G pixels[idx + 2] = color; // B pixels[idx + 3] = 255; // A } } // Create texture - automatically handles device loss if (!checkerTexture_.Create(service_, size, size, pixels.data())) { // Handle creation failure } } void OnRender() override { ImGui::Begin("Textured Panel"); // GetID() returns nullptr if device was lost (safe to check) void* texId = checkerTexture_.GetID(); if (texId) { ImGui::Image(texId, ImVec2(128, 128)); ImGui::Text("Texture displayed successfully"); } else { ImGui::TextColored(ImVec4(1, 0.4f, 0.2f, 1), "Texture unavailable"); } ImGui::End(); } void OnShutdown() override { // ImGuiTexture destructor automatically releases delete this; } private: cIGZImGuiService* service_; ImGuiTexture checkerTexture_; }; ``` -------------------------------- ### Acquire Terrain Decal Service Interface Source: https://github.com/caspervg/sc4-render-services/blob/main/docs/terrain-decals.md Demonstrates how to acquire the cIGZTerrainDecalService interface. The returned interface is AddRef'd and must be Release()'d by the caller. Fails on unsupported game versions. ```cpp #include "public/TerrainDecalServiceIds.h" #include "public/cIGZTerrainDecalService.h" cIGZTerrainDecalService* terrainDecalService = nullptr; if (fw->GetSystemService(kTerrainDecalServiceID, GZIID_cIGZTerrainDecalService, reinterpret_cast(&terrainDecalService))) { // use service terrainDecalService->Release(); } ``` -------------------------------- ### Project Initialization and 32-bit Enforcement Source: https://github.com/caspervg/sc4-render-services/blob/main/CMakeLists.txt Sets the minimum CMake version and forces a 32-bit Win32 build environment to ensure compatibility with SimCity 4. ```cmake cmake_minimum_required(VERSION 3.20) # Enable new MSVC runtime library selection policy if(POLICY CMP0091) cmake_policy(SET CMP0091 NEW) endif() # Force 32-bit build for SimCity 4 compatibility BEFORE project() call # Set the platform regardless of current setting to ensure 32-bit set(CMAKE_GENERATOR_PLATFORM "Win32") message(STATUS "Forcing 32-bit build for SC4 compatibility") # Additional 32-bit enforcement if(WIN32) set(CMAKE_SIZEOF_VOID_P 4) set(CMAKE_C_SIZEOF_DATA_PTR 4) set(CMAKE_CXX_SIZEOF_DATA_PTR 4) endif() project(SC4RenderServices VERSION 0.0.1 LANGUAGES CXX) set(SC4RS_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) ``` -------------------------------- ### Acquire SC4 Render Services Source: https://context7.com/caspervg/sc4-render-services/llms.txt Services are retrieved using cIGZFrameWork::GetSystemService. Always call Release() on the interface pointer after use to prevent memory leaks. ```cpp #include "public/ImGuiServiceIds.h" #include "public/S3DCameraServiceIds.h" #include "public/cIGZDrawService.h" #include "public/TerrainDecalServiceIds.h" // Acquire ImGui service cIGZImGuiService* imguiService = nullptr; if (fw->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService, reinterpret_cast(&imguiService))) { // Use service... imguiService->Release(); } // Acquire S3D Camera service cIGZS3DCameraService* cameraService = nullptr; if (fw->GetSystemService(kS3DCameraServiceID, GZIID_cIGZS3DCameraService, reinterpret_cast(&cameraService))) { // Use service... cameraService->Release(); } // Acquire Draw service cIGZDrawService* drawService = nullptr; if (fw->GetSystemService(kDrawServiceID, GZIID_cIGZDrawService, reinterpret_cast(&drawService))) { // Use service... drawService->Release(); } // Acquire Terrain Decal service cIGZTerrainDecalService* terrainDecalService = nullptr; if (fw->GetSystemService(kTerrainDecalServiceID, GZIID_cIGZTerrainDecalService, reinterpret_cast(&terrainDecalService))) { // Use service... terrainDecalService->Release(); } ``` -------------------------------- ### Minimal ImGui Panel Registration Source: https://github.com/caspervg/sc4-render-services/blob/main/README.md Register a minimal ImGui panel using a function pointer for the render callback. This method is suitable for simple UI elements. The service manages the panel's lifecycle after registration. ```cpp static void RenderPanel(void* data) { ImGui::Begin("My Panel"); ImGui::TextUnformatted("Hello from a client DLL."); ImGui::End(); } void RegisterPanel(cIGZFrameWork* fw) { cIGZImGuiService* service = nullptr; if (!fw->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService, reinterpret_cast(&service))) { return; } ImGuiPanelDesc desc{}; desc.id = 0x12345678; desc.order = 100; desc.visible = true; desc.on_render = &RenderPanel; desc.on_shutdown = nullptr; desc.data = nullptr; service->RegisterPanel(desc); service->Release(); } ``` -------------------------------- ### Class-Based ImGui Panel Registration Source: https://github.com/caspervg/sc4-render-services/blob/main/README.md Register a custom ImGui panel using a class that inherits from ImGuiPanel. This approach utilizes an adapter to create the ImGuiPanelDesc. Ensure the panel is deleted when no longer needed. ```cpp class MyPanel final : public ImGuiPanel { public: void OnInit() override {} void OnRender() override { ImGui::Begin("Class Panel"); ImGui::TextUnformatted("Hello from a class-based panel."); ImGui::End(); } }; void RegisterPanel(cIGZFrameWork* fw) { cIGZImGuiService* service = nullptr; if (!fw->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService, reinterpret_cast(&service))) { return; } auto* panel = new MyPanel(); ImGuiPanelDesc desc = ImGuiPanelAdapter::MakeDesc(panel, 0x12345678, 100, true); if (!service->RegisterPanel(desc)) { delete panel; } service->Release(); } ``` -------------------------------- ### Detecting and Handling DirectX Device Resets Source: https://github.com/caspervg/sc4-render-services/blob/main/README.md Illustrates how to detect DirectX device resets by monitoring the device generation. When the generation changes, textures become invalid and must be recreated. The RAII wrapper handles this automatically. ```cpp void OnRender() { static uint32_t lastGen = 0; uint32_t currentGen = service->GetDeviceGeneration(); if (currentGen != lastGen) { // Device was reset - recreate textures RecreateMyTextures(); lastGen = currentGen; } // Use textures normally... } ``` -------------------------------- ### Dependency Management Source: https://github.com/caspervg/sc4-render-services/blob/main/CMakeLists.txt Includes subdirectories for vendor libraries and validates the presence of required header files. ```cmake if(NOT TARGET spdlog) add_subdirectory(vendor/spdlog) endif() add_subdirectory(vendor/gzcom-dll) set(MINI_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/mINI/src") if (NOT EXISTS "${MINI_INCLUDE_DIR}/mini/ini.h") message( FATAL_ERROR "Missing vendor/mINI headers. Initialize submodules with: " "'git submodule update --init --recursive'." ) endif () ``` -------------------------------- ### Configure Texture and Material State Source: https://context7.com/caspervg/sc4-render-services/llms.txt Manages texture stages, filtering, wrapping, and environment modes for custom material rendering. ```cpp #include "public/cIGZDrawService.h" void ConfigureTextureState(cIGZDrawService* service, uint32_t textureId) { SC4DrawContextHandle ctx = service->WrapActiveRendererDrawContext(); if (!ctx.ptr) return; const int stage = 0; // Texture stage 0-3 // Enable texture stage service->EnableTextureStateFlag(ctx, true, stage); // Set texture service->SetTexture(ctx, textureId, stage); // Configure texture wrapping service->SetTexWrapModes(ctx, D3DTADDRESS_WRAP, D3DTADDRESS_WRAP, stage); // Configure texture filtering service->SetTexFiltering(ctx, D3DTFN_LINEAR, D3DTFN_LINEAR, stage); // Set texture color modulation service->SetTexColor(ctx, 1.0f, 1.0f, 1.0f, 1.0f); // Set texture environment mode service->SetTexEnvMode(ctx, D3DTOP_MODULATE, stage); // Clear texture transform (use identity) service->ClearTexTransform(ctx, stage); } ``` -------------------------------- ### CreateDecal Source: https://github.com/caspervg/sc4-render-services/blob/main/docs/terrain-decals.md Creates a new terrain decal in the active city. Requires a valid `TerrainDecalState` and returns a unique `TerrainDecalId` upon success. ```APIDOC ## POST /api/terrain/decals ### Description Creates a terrain decal at runtime. The main input type is `TerrainDecalState`. ### Method POST ### Endpoint /api/terrain/decals ### Parameters #### Request Body - **state** (TerrainDecalState) - Required - The state of the decal to create. - **textureKey** (cGZPersistResourceKey) - Required - Resource key for the decal texture. - **overlayType** (cISTETerrainView::tOverlayManagerType) - Required - The terrain overlay manager to use (e.g., `StaticLand`, `StaticWater`, `DynamicLand`, `DynamicWater`). - **decalInfo** (DecalInfo) - Required - Information about the decal's geometry and transform. - **center** (cS3DVector2) - Required - Decal position in world X/Z space. - **baseSize** (float) - Required - Base size of the decal footprint. - **rotationTurns** (float) - Required - Rotation in turns. - **aspectMultiplier** (float) - Optional - Aspect ratio multiplier. - **uvScaleU** (float) - Optional - UV scaling in U direction. - **uvScaleV** (float) - Optional - UV scaling in V direction. - **uvOffset** (float) - Optional - UV offset. - **opacity** (float) - Optional - Decal opacity (0.0 to 1.0). - **enabled** (bool) - Optional - Whether the decal is enabled. - **color** (cS3DVector3) - Optional - Decal color tint. - **drawMode** (int) - Optional - Rendering draw mode. - **hasUvWindow** (bool) - Optional - Indicates if a UV sub-rectangle is used. - **uvWindow** (TerrainDecalUvWindow) - Optional - UV sub-rectangle override. ### Request Example ```json { "textureKey": {"type": 0x7AB50E44, "group": 0x1ABE787D, "instance": 0xAA40173A}, "overlayType": "DynamicLand", "decalInfo": { "center": {"x": 512.0, "y": 512.0}, "baseSize": 16.0, "rotationTurns": 0.0, "aspectMultiplier": 1.0, "uvScaleU": 1.0, "uvScaleV": 1.0, "uvOffset": 0.0 }, "opacity": 1.0, "enabled": true, "color": {"x": 1.0, "y": 1.0, "z": 1.0} } ``` ### Response #### Success Response (200) - **id** (TerrainDecalId) - The unique identifier for the newly created decal. #### Response Example ```json { "id": "some-unique-decal-id" } ``` #### Error Response (400) - **error** (string) - Description of the error (e.g., "No active city", "Invalid state", "Missing overlay manager", "Version mismatch"). ``` -------------------------------- ### Manual ImGuiTexture API Usage Source: https://github.com/caspervg/sc4-render-services/blob/main/README.md Shows manual texture creation and management using ImGuiTextureDesc and ImGuiTextureHandle. The service stores pixel data internally. Remember to release the texture handle when done. Pixel data must remain valid during the CreateTexture call. ```cpp #include "public/cIGZImGuiService.h" // Create texture descriptor ImGuiTextureDesc desc{}; desc.width = 128; desc.height = 128; desc.pixels = myRGBA32Data; // Must remain valid during call desc.useSystemMemory = false; // Prefer video memory // Create texture - service stores pixel data internally ImGuiTextureHandle handle = service->CreateTexture(desc); if (handle.id == 0) { // Handle error } // In render loop: void* texId = service->GetTextureID(handle); if (texId) { ImGui::Image(texId, ImVec2(128, 128)); } // When done: service->ReleaseTexture(handle); ``` -------------------------------- ### QueueRender for One-Shot Callbacks Source: https://context7.com/caspervg/sc4-render-services/llms.txt Queues a callback for execution on the next ImGui frame. The cleanup callback is mandatory for managing memory allocated for the render data. ```cpp #include "public/cIGZImGuiService.h" struct DeferredRenderData { const char* message; int priority; }; static void OneShotRenderCallback(void* data) { auto* renderData = static_cast(data); ImGui::Begin("One-Shot Notification"); ImGui::Text("Message: %s", renderData->message); ImGui::Text("Priority: %d", renderData->priority); ImGui::End(); } static void CleanupRenderData(void* data) { auto* renderData = static_cast(data); delete renderData; } void QueueDeferredNotification(cIGZImGuiService* service) { auto* data = new DeferredRenderData{"Important notification!", 5}; // Queue callback - runs on next ImGui frame // Cleanup runs after callback or during shutdown if still queued service->QueueRender(OneShotRenderCallback, data, CleanupRenderData); } ``` -------------------------------- ### S3D Camera Service Source: https://github.com/caspervg/sc4-render-services/blob/main/docs/services.md Provides an interface to manage cameras, including wrapping existing game cameras, handling projection, and transforming world coordinates to screen coordinates. It's crucial to call these methods on the main or render thread only. ```APIDOC ## S3D Camera Service ### Description Manages cameras, including wrapping game cameras, projection, and coordinate transformations. ### Interface IDs - Service ID: `kS3DCameraServiceID` - Interface ID: `GZIID_cIGZS3DCameraService` - Interface Header: `src/public/cIGZS3DCameraService.h` ### Camera Handles Handles are version-tagged to prevent cross-build usage. - `WrapCamera`: Returns a non-owned handle for a game-managed camera. - `WrapActiveRendererCamera`: Returns the active renderer camera if available. - `CreateCamera`: Not supported by the built-in service; returns a null handle. - `DestroyCamera`: No-op for wrapped handles. ### Projection Helpers - `Project`, `UnProject`, `WorldToScreen`: Thin wrappers around the game camera. - Functions return false or null for invalid handles or unavailable cameras. ### Viewport and Transforms Viewport, subview, ortho, view volume, and transform helpers map to `cS3DCamera` methods. ### Usage Snippet ```cpp cIGZS3DCameraService* cameraService = nullptr; if (!fw->GetSystemService(kS3DCameraServiceID, GZIID_cIGZS3DCameraService, reinterpret_cast(&cameraService))) { return; } auto handle = cameraService->WrapActiveRendererCamera(); float sx = 0.0f; float sy = 0.0f; if (cameraService->WorldToScreen(handle, worldX, worldY, worldZ, sx, sy)) { // use screen coordinates } cameraService->Release(); ``` ### Larger Examples - `../src/sample/WorldProjectionSampleDirector.cpp` - `../src/sample/S3DCameraDebugSampleDirector.cpp` ```