Map: per-track industry labels rendered via Dear ImGui on both the popout and in-game overlay. Three zoom stages - per-track labels, merged same-name clusters, and one-per-industry labels at wider zoom. Settings: font size (min/max with zoom scaling), leader lines, parallel rotation, collision spread, utility track filters (repair/diesel/loader/interchange), zoom thresholds per category. C# runs a world-space AABB solver at rebuild so offsets are rotation-invariant; native runs a per-frame screen-space fine-tuning pass and draws leader lines. Mesh LOD: new module skeleton with 4-level LOD group injected on HandleModelsLoaded - three progressive renderer cull tiers (sorted by bounds volume) plus a 12-tri proxy box at max distance. lodBias-corrected distance-to-screenHeight conversion. Debounced live refresh on settings change. Disabled by default.
310 lines
18 KiB
C#
310 lines
18 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace S3.Modules.Popout {
|
|
|
|
// Must match InputEvent in native/include/shared_types.h exactly.
|
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
|
public struct InputEvent {
|
|
public int type; // InputEventType (0=Move,1=LDown,2=LUp,3=RDown,4=RUp,5=Wheel)
|
|
public float x; // normalized [0,1] left=0
|
|
public float y; // normalized [0,1] top=0
|
|
public float delta; // wheel delta; positive = scroll up
|
|
}
|
|
|
|
public enum InputEventType {
|
|
MouseMove = 0,
|
|
LButtonDown = 1,
|
|
LButtonUp = 2,
|
|
RButtonDown = 3,
|
|
RButtonUp = 4,
|
|
MouseWheel = 5,
|
|
UICommand = 6, // toolbar button; x = UICmd value cast to float
|
|
}
|
|
|
|
public enum UICmd {
|
|
Recenter = 1,
|
|
FollowMode = 2, // toggle MapEnhancer follow on/off
|
|
FollowPlayerCam = 3, // follow Camera.main continuously
|
|
FollowSelectedLoco = 4, // follow TrainController.Shared.SelectedCar
|
|
FollowLoco = 5, // follow specific loco; InputEvent.y = 0-based index
|
|
JumpToLocation = 6, // jump to named location; InputEvent.y = 0-based index
|
|
SetRotation = 7, // set map rotation; InputEvent.y = degrees [0,360)
|
|
RotateReset = 8, // reset map rotation to 0
|
|
RotateSyncPlayer = 9, // toggle sync-rotation-to-player-camera mode
|
|
ToggleFollowPlayer = 10, // toggle continuous follow of player world position
|
|
PopOut = 11, // (in-game only) close the overlay and open the OS popout
|
|
SetTheme = 12, // (in-game only) apply theme preset; InputEvent.y = preset index
|
|
SetAlpha = 13, // (in-game only) set chrome alpha; InputEvent.y = [0.1, 1.0]
|
|
Close = 14, // (in-game only) user clicked [X] — hide the overlay
|
|
SetMapAlpha = 15, // (in-game only) set map image alpha; InputEvent.y = [0.0, 1.0]
|
|
// MapEnhancer settings
|
|
SetMEBool = 16, // toggle ME bool setting; y = MEBoolBit index, delta = 0|1
|
|
SetMEFloat = 17, // set ME float setting; y = MEFloatIdx index, delta = value
|
|
MEResetSwitchesNormal = 18, // bulk reset all switches to Normal
|
|
MEResetSwitchesThrown = 19, // bulk reset all switches to Thrown
|
|
TogglePanDisablesFollow = 20, // toggle whether panning cancels follow mode
|
|
ToggleRightClickRecenter = 21, // toggle whether right-clicking recenters on player
|
|
SetMapBgAlpha = 22, // set map camera clear-colour opacity; y = [0.0, 1.0]
|
|
// Icon culling + EOTD (gear menu Icons submenu)
|
|
ToggleIconCulling = 23,
|
|
SetCullThreshold = 24, // y = [0.3, 1.0]
|
|
ToggleHideMuLocos = 25,
|
|
SetLocoBoostedScale = 26, // y = [1.0, 3.0]
|
|
ToggleEotd = 27,
|
|
ToggleEotdOnlyWhenCulled = 28,
|
|
SetEotdSizeScale = 29, // y = [1.0, 10.0]
|
|
ToggleTrackLabels = 30, // toggle industry track name labels on the map
|
|
TrackLabelSetFontSize = 31, // y = font size px [8, 24]
|
|
TrackLabelSetLineThick = 32, // y = leader line thickness [1, 4]
|
|
TrackLabelSetZoomLimit = 33, // y = orthographicSize zoom-out limit [200, 8000]
|
|
ToggleLeaderLines = 34, // toggle leader lines from label to track anchor
|
|
ToggleCollision = 35, // toggle collision-avoidance label spread
|
|
ToggleParallelLabels = 36, // rotate labels to run parallel to their track
|
|
ToggleMergeLabels = 37, // merge same-name nearby spans into one label
|
|
TrackLabelSetMergeZoom = 38, // y = orthographicSize threshold for auto-merge
|
|
TrackLabelSetIndustryZoom = 39, // y = orthographicSize threshold for Stage 3 industry labels
|
|
ToggleUtilityRepairLabels = 40, // toggle repair-track labels
|
|
ToggleUtilityDieselLabels = 41, // toggle diesel-stand labels
|
|
ToggleUtilityLoaderLabels = 42, // toggle coal-loader labels
|
|
ToggleUtilityInterchangeLabels = 43, // toggle interchange labels
|
|
TrackLabelSetUtilityZoom = 44, // y = orthographicSize beyond which utility labels hide
|
|
TrackLabelSetAllZoom = 45, // y = orthographicSize beyond which ALL labels hide
|
|
ToggleAvoidTrackLabels = 46, // push labels off their own track line
|
|
TrackLabelSetFontSizeMin = 47, // y = minimum font size px [4, max]; labels auto-scale with zoom
|
|
}
|
|
|
|
// Must match MapThemeData in native/include/shared_types.h exactly (36 floats = 144 bytes).
|
|
[Serializable]
|
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
|
public struct MapThemeData {
|
|
public float wBgR, wBgG, wBgB, wBgA; // window / toolbar background
|
|
public float accR, accG, accB, accA; // accent (TitleBgActive, buttons)
|
|
public float txtR, txtG, txtB, txtA; // status text
|
|
public float popR, popG, popB, popA; // settings popup background
|
|
public float mapR, mapG, mapB, mapA; // map image tint (1,1,1,1 = no tint)
|
|
public float cmpR, cmpG, cmpB, cmpA; // compass background disk
|
|
public float nNR, nNG, nNB, nNA; // compass needle N half
|
|
public float nSR, nSG, nSB, nSA; // compass needle S half
|
|
public float mapBgR, mapBgG, mapBgB, mapBgA; // map camera clear colour
|
|
}
|
|
|
|
internal static class Native {
|
|
// S3Native.dll lives in the mod folder (Mods/S3) and is loaded by full path
|
|
// via NativeLoader before any of these are called; once loaded, [DllImport]
|
|
// binds to it by name. (Pure-UMM install — no game-root copy, no winhttp proxy.)
|
|
// Named S3Native (not RRPopout) so it can never collide with a leftover copy
|
|
// of the old standalone PopOut's RRPopout.dll still injected in someone's game.
|
|
private const string Dll = "S3Native";
|
|
|
|
// Create a floating window. Returns a handle (> 0) or 0 on failure.
|
|
// Native loads saved position/size from its state file; width/height are the first-run defaults.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
|
public static extern int RRPOPOUT_CreateWindow(string title, int width, int height);
|
|
|
|
// Set the source texture and the UV sub-rect to blit next frame.
|
|
// Call this on the main thread immediately before IssuePluginEvent.
|
|
// u0,v0 = top-left UV in D3D convention (V=0 at top); u1,v1 = bottom-right.
|
|
// Pass (0, 0, 1, 1) for full texture.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetFrameTexture(
|
|
int windowHandle, IntPtr texturePtr,
|
|
float u0, float v0, float u1, float v1);
|
|
|
|
// Returns the UnityRenderingEvent callback pointer for GL.IssuePluginEvent.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern IntPtr RRPOPOUT_GetRenderEventFunc();
|
|
|
|
// Fills outWidth / outHeight with the current client area size.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_GetWindowSize(int windowHandle, out int outWidth, out int outHeight);
|
|
|
|
// Drains queued input events from the OS window into outEvents.
|
|
// Returns the number of events written (≤ maxEvents).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern int RRPOPOUT_PollInputEvents(
|
|
int windowHandle,
|
|
[Out] InputEvent[] outEvents,
|
|
int maxEvents);
|
|
|
|
// Close and free the window.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_DestroyWindow(int windowHandle);
|
|
|
|
// Update the status bar text shown at the bottom of the popout.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
|
public static extern void RRPOPOUT_SetStatusText(int windowHandle, string text);
|
|
|
|
// Set the locomotive list shown in the Follow submenu.
|
|
// names: loco display names joined by '\n'.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
|
public static extern void RRPOPOUT_SetLocoList(int windowHandle, string names);
|
|
|
|
// Set the location list shown in the Jump to location submenu.
|
|
// names: location names joined by '\n'.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
|
public static extern void RRPOPOUT_SetLocationList(int windowHandle, string names);
|
|
|
|
// Tell the native UI whether MapEnhancer is installed (controls menu layout).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetMapEnhancerInstalled(int windowHandle, bool installed);
|
|
|
|
// Push ME capability flags to native.
|
|
// hasTurntable — ShowTurntableMarkers exists (v1.6.0 or community); gates Turntable Markers.
|
|
// community — full community build (crossing/spawn/switch-reset); gates community items.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetMapEnhancerCaps(int windowHandle, bool hasTurntable, bool community);
|
|
|
|
// Update the compass display to reflect the current map rotation (degrees).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetMapRotation(int windowHandle, float degrees);
|
|
|
|
// Highlight (or un-highlight) the sync button in the toolbar.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetMapSyncPlayer(int windowHandle, bool active);
|
|
|
|
// Highlight (or un-highlight) the follow-player toolbar button.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetFollowPlayer(int windowHandle, bool active);
|
|
|
|
// Push current map zoom so native can persist it on window close.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetMapZoom(int windowHandle, float zoom);
|
|
|
|
// Read back the C#-side state that native loaded from its save file.
|
|
// followPlayer / syncRotation return 1 (true) or 0 (false).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_GetPersistedState(int windowHandle,
|
|
out int followPlayer, out int syncRotation,
|
|
out float mapRotation, out float mapZoom);
|
|
|
|
// -------------------------------------------------------------------
|
|
// In-game overlay. Shares the native engine's shared ImGui context
|
|
// and D3D11 device; renders into Unity's bound backbuffer after frame.
|
|
// -------------------------------------------------------------------
|
|
|
|
// Event id to pass to GL.IssuePluginEvent to drive the overlay render.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern int RRPOPOUT_GetOverlayEventId();
|
|
|
|
// One-time: hand native a texture so it can acquire Unity's D3D device.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetOverlayDeviceTexture(IntPtr texturePtr);
|
|
|
|
// Per-frame input snapshot (top-left origin, pixels; wheel in WHEEL_DELTA units).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetOverlayInput(
|
|
float displayW, float displayH,
|
|
float mouseX, float mouseY,
|
|
int lButton, int rButton, int wheel);
|
|
|
|
// Show or hide the overlay.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetOverlayVisible(int visible);
|
|
|
|
// Map render texture to display inside the in-game overlay window, plus the
|
|
// UV sub-rect (V flipped for a Unity RT: v0=1, v1=0). IntPtr.Zero clears it.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetOverlayMapTexture(
|
|
IntPtr texturePtr, float u0, float v0, float u1, float v1);
|
|
|
|
// 1 when ImGui wants the mouse this frame (hovering a widget).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern int RRPOPOUT_OverlayWantsMouse();
|
|
|
|
// Drains queued in-game map input (drag/zoom over the map image). Events are
|
|
// in normalized [0,1] image space (top-left origin); forward to the map camera.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern int RRPOPOUT_PollOverlayInput(
|
|
[Out] InputEvent[] outEvents, int maxEvents);
|
|
|
|
// Reads the map image region size (px) so C# can set camera.aspect to match,
|
|
// filling the window with no letterbox bars.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_GetOverlayMapView(out float outW, out float outH);
|
|
|
|
// Handle of the in-game overlay's shared UI-state holder. Push state to it
|
|
// (status/loco/location/rotation/follow) and poll it for toolbar commands
|
|
// with the same handle-based functions the popout uses.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern int RRPOPOUT_GetOverlayWindowHandle();
|
|
|
|
// Apply a theme to the shared ImGui context. Affects both the in-game overlay
|
|
// and the popout (they share one context). presetIndex is stored for the gear
|
|
// menu checkmarks. Thread-safe: stored on main thread, applied on render thread.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetTheme(ref MapThemeData data, int presetIndex);
|
|
|
|
// Set the in-game overlay's chrome alpha [0.1, 1.0]. Thread-safe via atomic.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetOverlayAlpha(float alpha);
|
|
|
|
// Set the map image alpha [0.0, 1.0]. Independent of chrome alpha.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetOverlayMapAlpha(float alpha);
|
|
|
|
// Tell native whether panning should cancel follow mode (for the gear menu checkmark).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetPanDisablesFollow(int windowHandle, bool active);
|
|
|
|
// Tell native whether right-clicking the map recenters on the player (gear menu checkmark).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetRightClickRecenter(int windowHandle, bool active);
|
|
|
|
// Seed all icon-culling and EOTD state for the gear menu Icons submenu.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetIconCullingState(int windowHandle,
|
|
bool cullEnabled, float cullThresh, bool hideMU, float locoBoost,
|
|
bool eotdEnabled, bool eotdOnlyWhenCulled, float eotdSize);
|
|
|
|
// Seed the map camera clear-colour opacity slider (shared global, overlay + popout).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetOverlayMapBgAlpha(float alpha);
|
|
|
|
// Push MapEnhancer settings state to a window's render-thread atomics.
|
|
// flags: bit-packed booleans (see MEBoolBit in shared_types.h).
|
|
// Scale values seed slider positions; sliders update them live during drag
|
|
// and push UICmd::SetMEFloat only on release (IsItemDeactivatedAfterEdit).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetMEState(
|
|
int windowHandle, uint flags,
|
|
float flareScale, float junctionScale,
|
|
float trackThickness, float crossingScale);
|
|
|
|
// Push industry track name labels for the map.
|
|
// namesBlob: track names joined by '\n'.
|
|
// us/vs: centroid UV per label (C# projects TrackSpan centroid through the map camera).
|
|
// anchorUs/anchorVs: flat UV arrays for all anchor span positions across all labels.
|
|
// anchorStarts/anchorCounts: per-label start index and count into the anchor arrays.
|
|
// count=0 clears everything (call when zoomed out or map inactive).
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
|
public static extern void RRPOPOUT_SetTrackLabels(
|
|
int windowHandle, string namesBlob,
|
|
[In] float[] us, [In] float[] vs,
|
|
[In] float[] anchorUs, [In] float[] anchorVs,
|
|
[In] int[] anchorStarts, [In] int[] anchorCounts,
|
|
[In] float[] angles, // screen-space angle per label (degrees; 0=right, +ve=CW in screen space)
|
|
[In] float[] scales, // font size multiplier per label (1.0=track label, 1.5=industry label)
|
|
int count);
|
|
|
|
// Enable or disable track label rendering for this window. Default: enabled.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetTrackLabelsEnabled(int windowHandle, bool enabled);
|
|
|
|
// Seed all track-label style settings at once. Called on map activate and
|
|
// whenever any of the settings change via the gear menu.
|
|
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern void RRPOPOUT_SetTrackLabelStyle(
|
|
int windowHandle,
|
|
float fontSize, float lineThickness, float zoomLimit,
|
|
bool leaderLinesEnabled, bool collisionEnabled, bool parallelEnabled,
|
|
bool mergeEnabled, float mergeZoom,
|
|
float industryZoom,
|
|
bool utilityRepairEnabled, bool utilityDieselEnabled,
|
|
bool utilityLoaderEnabled, bool utilityInterchangeEnabled,
|
|
float utilityZoom,
|
|
float allLabelsZoom,
|
|
bool avoidTrack,
|
|
float fontSizeMin);
|
|
}
|
|
}
|