From b533bb3280470adbb71221ec8bda1f0f2a513280 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Wed, 27 May 2026 18:42:21 +1200
Subject: [PATCH 01/20] Copy files across from branch (not fork - I'm new to
git, whoops)
---
.gitignore | 3 +
SCANmechjeb/SCANmechjeb.cs | 47 +++-
SCANmechjeb/SCANmechjeb.csproj | 19 +-
SCANsat.Unity/Interfaces/ISCAN_MainMap.cs | 9 +-
SCANsat.Unity/SCANsat.Unity.csproj | 6 +-
SCANsat.Unity/Unity/SCAN_MainMap.cs | 81 +++++-
SCANsat.sln | 247 ------------------
SCANsat.slnx | 213 +++++++++++++++
SCANsat/SCAN_Color_Config.cs | 3 -
SCANsat/SCAN_Data/SCANdata.cs | 43 +--
SCANsat/SCAN_Data/SCANresourceGlobal.cs | 2 +-
SCANsat/SCAN_Map/SCANmap.cs | 136 +++-------
SCANsat/SCAN_Map/SCANmapExporter.cs | 2 +-
SCANsat/SCAN_Map/SCANmapLegend.cs | 6 +-
SCANsat/SCAN_PartModules/SCANsat.cs | 2 +-
.../ConfigNodes/ConfigExtensions.cs | 2 +-
SCANsat/SCAN_Platform/Logging.cs | 2 +-
SCANsat/SCAN_Reflection/SCANkopernicus.cs | 2 +-
.../SCAN_Reflection/SCANparallaxContinued.cs | 2 +-
SCANsat/SCAN_Unity/SCAN_UI_Color.cs | 10 +-
SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs | 221 ++++++----------
SCANsat/SCAN_Unity/SCAN_UI_Overlay.cs | 2 +-
SCANsat/SCANcontroller.cs | 196 ++++++--------
SCANsat/SCANmainMenuLoader.cs | 2 +-
SCANsat/SCANsat.csproj | 13 +-
SCANsat/SCANutil.cs | 70 ++++-
26 files changed, 640 insertions(+), 701 deletions(-)
delete mode 100644 SCANsat.sln
create mode 100644 SCANsat.slnx
diff --git a/.gitignore b/.gitignore
index 197a1a8fa..2e20bb5a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,9 @@
bin/
obj/
*.user
+*.dll
+*.pdb
+SCANsat.Unity/Properties/launchSettings.json
GameData/SCANsat/Plugins/SCAN*
GameData/SCANsat/PluginData/Settings.cfg
GameData/SCANsat/Parts/@thumbs/
diff --git a/SCANmechjeb/SCANmechjeb.cs b/SCANmechjeb/SCANmechjeb.cs
index f98c2c734..579bd0f4f 100644
--- a/SCANmechjeb/SCANmechjeb.cs
+++ b/SCANmechjeb/SCANmechjeb.cs
@@ -14,8 +14,7 @@
using System.Collections;
using SCANsat;
using SCANsat.SCAN_Data;
-using log = SCANsat.SCAN_Platform.Logging.ConsoleLogger;
-using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
+using Log = KSPCommunityLib.Logging.Log;
using MuMech;
using UnityEngine;
@@ -36,6 +35,43 @@ class SCANmechjeb : MonoBehaviour
private Vector2d coords = new Vector2d();
private bool shutdown, mjOnboard, mjTechTreeLocked;
+ #region Helpers
+
+ ///
+ /// Reflectively fetches MechJebCore's target controller, tolerating the
+ /// 'Target'/'target' field rename across MechJeb versions. Returns null
+ /// (and logs) if the field can't be resolved under any known name.
+ ///
+ private static MechJebModuleTargetController GetMJCoreTarget(MechJebCore mjc)
+ {
+ var t = mjc.GetType();
+ var field = t.GetField("Target") ?? t.GetField("target");
+ if (field == null)
+ {
+ Log.Message("MechJebCore 'target' field could not be found under any known name; MechJeb support broken.");
+ }
+
+ return field?.GetValue(mjc) as MechJebModuleTargetController;
+ }
+
+ ///
+ /// Reflectively reads the guidance module's hidden flag, tolerating the
+ /// 'Hidden'/'hidden' field rename across MechJeb versions. Returns null
+ /// (and logs) if the field can't be resolved under any known name.
+ ///
+ private static bool? IsMJGuidanceModuleHidden(DisplayModule gm)
+ {
+ var t = gm.GetType();
+ var field = t.GetField("Hidden") ?? t.GetField("hidden");
+ if (field == null)
+ {
+ Log.Message("MechJebGuidanceModule 'hidden' field could not be found under any known name; MechJeb support broken.");
+ }
+ return field?.GetValue(gm) as bool?;
+ }
+
+ #endregion
+
private void Start()
{
GameEvents.onVesselWasModified.Add(VesselChange);
@@ -227,7 +263,7 @@ private void RefereshAfterVesselChange()
return;
}
- target = mjCore.target;
+ target = GetMJCoreTarget(mjCore);
if (target == null)
{
@@ -256,7 +292,8 @@ private void RefereshAfterVesselChange()
guidanceModule.UnlockCheck();
- if (guidanceModule.hidden)
+ var hidden = IsMJGuidanceModuleHidden(guidanceModule);
+ if (hidden ?? true) // Defaults to a hidden state if the field cannot be found
{
SCANcontroller.controller.MechJebLoaded = false;
way = null;
@@ -270,4 +307,4 @@ private void RefereshAfterVesselChange()
mjTechTreeLocked = false;
}
}
-}
+}
\ No newline at end of file
diff --git a/SCANmechjeb/SCANmechjeb.csproj b/SCANmechjeb/SCANmechjeb.csproj
index 161bf44a4..f673f0ebc 100644
--- a/SCANmechjeb/SCANmechjeb.csproj
+++ b/SCANmechjeb/SCANmechjeb.csproj
@@ -3,23 +3,22 @@
Library
SCANmechjeb
SCANmechjeb
- net4.8
+ net48
- true
- true
+ true
+ true
-
- False
- $(KSPRoot)\GameData\MechJeb2\Plugins\MechJeb2.dll
- False
+
+ GameData/MechJeb2/Plugins/MechJeb2.dll
MechJeb2
- 2.5
-
+ 2.5
+
-
+
+
diff --git a/SCANsat.Unity/Interfaces/ISCAN_MainMap.cs b/SCANsat.Unity/Interfaces/ISCAN_MainMap.cs
index 677e23487..3c3a66b44 100644
--- a/SCANsat.Unity/Interfaces/ISCAN_MainMap.cs
+++ b/SCANsat.Unity/Interfaces/ISCAN_MainMap.cs
@@ -17,6 +17,13 @@
namespace SCANsat.Unity.Interfaces
{
+ public enum MainMapDisplayMode
+ {
+ Terrain = 0,
+ Visual = 1,
+ Biome = 2
+ }
+
public interface ISCAN_MainMap
{
string Version { get; }
@@ -27,7 +34,7 @@ public interface ISCAN_MainMap
bool TerminatorToggle { get; set; }
- bool MapType { get; set; }
+ MainMapDisplayMode MapType { get; set; }
bool Minimized { get; set; }
diff --git a/SCANsat.Unity/SCANsat.Unity.csproj b/SCANsat.Unity/SCANsat.Unity.csproj
index cc67fc8bd..09ff5a8a0 100644
--- a/SCANsat.Unity/SCANsat.Unity.csproj
+++ b/SCANsat.Unity/SCANsat.Unity.csproj
@@ -6,9 +6,11 @@
net4.8
- false
+ false
+ true
-
+
+
\ No newline at end of file
diff --git a/SCANsat.Unity/Unity/SCAN_MainMap.cs b/SCANsat.Unity/Unity/SCAN_MainMap.cs
index 52c06d1d7..164d35e68 100644
--- a/SCANsat.Unity/Unity/SCAN_MainMap.cs
+++ b/SCANsat.Unity/Unity/SCAN_MainMap.cs
@@ -140,7 +140,28 @@ public void setMap(ISCAN_MainMap map)
if (m_TypeToggle != null)
{
- m_TypeToggle.isOn = map.MapType;
+ // Map display mode to toggle state for UI purposes
+ // We'll cycle through modes on click
+ m_TypeToggle.interactable = true;
+ }
+
+ if (m_TypeLabel != null)
+ {
+ // Show initial display mode
+ string label = "Terrain";
+ switch (map.MapType)
+ {
+ case (MainMapDisplayMode.Terrain):
+ label = "Terrain";
+ break;
+ case (MainMapDisplayMode.Biome):
+ label = "Biome";
+ break;
+ case (MainMapDisplayMode.Visual):
+ label = "Visual";
+ break;
+ }
+ m_TypeLabel.OnTextUpdate.Invoke(label);
}
if (m_MinimizeToggle != null)
@@ -148,19 +169,13 @@ public void setMap(ISCAN_MainMap map)
m_MinimizeToggle.isOn = map.Minimized;
}
- //if (m_M700Text != null && !map.ResourcesOn)
- // m_M700Text.gameObject.SetActive(false);
-
- //if (m_OreText != null && !map.ResourcesOn)
- // m_OreText.gameObject.SetActive(false);
-
CreateVessels(map.VesselInfoList);
SetScale(map.Scale);
SetPosition(map.Position);
- if (!map.MapType)
+ if (map.MapType == MainMapDisplayMode.Terrain)
{
SetGeneratingText(map.MapGenerating);
}
@@ -525,17 +540,57 @@ public void ToggleTerminator(bool isOn)
public void ToggleType(bool isOn)
{
- if (m_TypeLabel != null)
+ if (!loaded || mapInterface == null)
{
- m_TypeLabel.OnTextUpdate.Invoke(isOn ? "Biome" : "Terrain");
+ return;
}
- if (!loaded || mapInterface == null)
+ // Cycle through display modes: Terrain -> Visual -> Biome -> Terrain
+ MainMapDisplayMode currentMode = mapInterface.MapType;
+ MainMapDisplayMode nextMode;
+
+ switch (currentMode)
{
- return;
+ case MainMapDisplayMode.Terrain:
+ nextMode = MainMapDisplayMode.Visual;
+ break;
+ case MainMapDisplayMode.Visual:
+ nextMode = MainMapDisplayMode.Biome;
+ break;
+ case MainMapDisplayMode.Biome:
+ nextMode = MainMapDisplayMode.Terrain;
+ break;
+ default:
+ nextMode = MainMapDisplayMode.Terrain;
+ break;
+ }
+
+ mapInterface.MapType = nextMode;
+
+ // Update label
+ if (m_TypeLabel != null)
+ {
+ string label = "";
+ switch (nextMode)
+ {
+ case (MainMapDisplayMode.Terrain):
+ label = "Terrain";
+ break;
+ case (MainMapDisplayMode.Biome):
+ label = "Biome";
+ break;
+ case (MainMapDisplayMode.Visual):
+ label = "Visual";
+ break;
+ }
+ m_TypeLabel.OnTextUpdate.Invoke(label);
}
- mapInterface.MapType = isOn;
+ // Update toggle appearance (cycle through states visually)
+ if (m_TypeToggle != null)
+ {
+ m_TypeToggle.isOn = (nextMode != MainMapDisplayMode.Terrain);
+ }
}
public void ToggleSize(bool isOn)
diff --git a/SCANsat.sln b/SCANsat.sln
deleted file mode 100644
index 94f9398d0..000000000
--- a/SCANsat.sln
+++ /dev/null
@@ -1,247 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.11.35312.102
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SCANsat", "SCANsat\SCANsat.csproj", "{3AE13948-477D-4375-8AB4-B45664966D9E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SCANmechjeb", "SCANmechjeb\SCANmechjeb.csproj", "{A2B942AF-3AB2-4A9F-9211-FFC47364128D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SCANsat.Unity", "SCANsat.Unity\SCANsat.Unity.csproj", "{4ED5E40D-273A-4643-9009-04C1DD02F3E2}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{63914E47-3B9A-4298-BAA4-362B47D8F105}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- .gitignore = .gitignore
- CHANGELOG.md = CHANGELOG.md
- CONTRIBUTING.md = CONTRIBUTING.md
- LICENSE.txt = LICENSE.txt
- README.md = README.md
- SCANsat.props = SCANsat.props
- SCANsat.props.user = SCANsat.props.user
- SCANsat.version.props = SCANsat.version.props
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {3AE13948-477D-4375-8AB4-B45664966D9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3AE13948-477D-4375-8AB4-B45664966D9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3AE13948-477D-4375-8AB4-B45664966D9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3AE13948-477D-4375-8AB4-B45664966D9E}.Release|Any CPU.Build.0 = Release|Any CPU
- {A2B942AF-3AB2-4A9F-9211-FFC47364128D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A2B942AF-3AB2-4A9F-9211-FFC47364128D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A2B942AF-3AB2-4A9F-9211-FFC47364128D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A2B942AF-3AB2-4A9F-9211-FFC47364128D}.Release|Any CPU.Build.0 = Release|Any CPU
- {4ED5E40D-273A-4643-9009-04C1DD02F3E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4ED5E40D-273A-4643-9009-04C1DD02F3E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4ED5E40D-273A-4643-9009-04C1DD02F3E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4ED5E40D-273A-4643-9009-04C1DD02F3E2}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {316FD7A6-78EB-48DD-92DE-595FE8DD957D}
- EndGlobalSection
- GlobalSection(MonoDevelopProperties) = preSolution
- StartupItem = SCANdebug\SCANdebug.csproj
- Policies = $0
- $0.DotNetNamingPolicy = $1
- $1.DirectoryNamespaceAssociation = None
- $1.ResourceNamePolicy = FileFormatDefault
- $0.StandardHeader = $2
- $2.Text =
- $2.IncludeInNewFiles = True
- $0.TextStylePolicy = $39
- $3.inheritsSet = null
- $3.scope = text/x-csharp
- $0.CSharpFormattingPolicy = $4
- $4.IndentSwitchBody = True
- $4.NamespaceBraceStyle = EndOfLine
- $4.ClassBraceStyle = EndOfLine
- $4.InterfaceBraceStyle = EndOfLine
- $4.StructBraceStyle = EndOfLine
- $4.EnumBraceStyle = EndOfLine
- $4.MethodBraceStyle = EndOfLine
- $4.ConstructorBraceStyle = EndOfLine
- $4.DestructorBraceStyle = EndOfLine
- $4.WithinMethodDeclarationParentheses = True
- $4.WithinConstructorDeclarationParentheses = True
- $4.BeforeIndexerDeclarationBracket = False
- $4.AfterDelegateDeclarationParameterComma = True
- $4.WithinDelegateDeclarationParentheses = True
- $4.AroundShiftOperatorParentheses = False
- $4.SpacesBeforeBrackets = False
- $4.BlankLinesBetweenTypes = 2
- $4.inheritsSet = Mono
- $4.inheritsScope = text/x-csharp
- $4.scope = text/x-csharp
- $5.FileWidth = 120
- $5.TabWidth = 2
- $5.NoTabsAfterNonTabs = True
- $5.EolMarker = Windows
- $5.inheritsSet = Mono
- $5.inheritsScope = text/plain
- $5.scope = text/plain
- $6.inheritsSet = null
- $6.scope = application/octet-stream
- $0.NameConventionPolicy = $7
- $7.Rules = $8
- $8.NamingRule = $33
- $9.Name = Namespaces
- $9.AffectedEntity = Namespace
- $9.VisibilityMask = VisibilityMask
- $9.NamingStyle = PascalCase
- $9.IncludeInstanceMembers = True
- $9.IncludeStaticEntities = True
- $10.Name = Types
- $10.AffectedEntity = Class, Struct, Enum, Delegate
- $10.VisibilityMask = VisibilityMask
- $10.NamingStyle = PascalCase
- $10.IncludeInstanceMembers = True
- $10.IncludeStaticEntities = True
- $11.Name = Interfaces
- $11.RequiredPrefixes = $12
- $12.String = I
- $11.AffectedEntity = Interface
- $11.VisibilityMask = VisibilityMask
- $11.NamingStyle = PascalCase
- $11.IncludeInstanceMembers = True
- $11.IncludeStaticEntities = True
- $13.Name = Attributes
- $13.RequiredSuffixes = $14
- $14.String = Attribute
- $13.AffectedEntity = CustomAttributes
- $13.VisibilityMask = VisibilityMask
- $13.NamingStyle = PascalCase
- $13.IncludeInstanceMembers = True
- $13.IncludeStaticEntities = True
- $15.Name = Event Arguments
- $15.RequiredSuffixes = $16
- $16.String = EventArgs
- $15.AffectedEntity = CustomEventArgs
- $15.VisibilityMask = VisibilityMask
- $15.NamingStyle = PascalCase
- $15.IncludeInstanceMembers = True
- $15.IncludeStaticEntities = True
- $17.Name = Exceptions
- $17.RequiredSuffixes = $18
- $18.String = Exception
- $17.AffectedEntity = CustomExceptions
- $17.VisibilityMask = VisibilityMask
- $17.NamingStyle = PascalCase
- $17.IncludeInstanceMembers = True
- $17.IncludeStaticEntities = True
- $19.Name = Methods
- $19.AffectedEntity = Methods
- $19.VisibilityMask = VisibilityMask
- $19.NamingStyle = PascalCase
- $19.IncludeInstanceMembers = True
- $19.IncludeStaticEntities = True
- $20.Name = Static Readonly Fields
- $20.AffectedEntity = ReadonlyField
- $20.VisibilityMask = Internal, Protected, Public
- $20.NamingStyle = PascalCase
- $20.IncludeInstanceMembers = False
- $20.IncludeStaticEntities = True
- $21.Name = Fields (Non Private)
- $21.AffectedEntity = Field
- $21.VisibilityMask = Internal, Protected, Public
- $21.NamingStyle = PascalCase
- $21.IncludeInstanceMembers = True
- $21.IncludeStaticEntities = True
- $22.Name = ReadOnly Fields (Non Private)
- $22.AffectedEntity = ReadonlyField
- $22.VisibilityMask = Internal, Protected, Public
- $22.NamingStyle = PascalCase
- $22.IncludeInstanceMembers = True
- $22.IncludeStaticEntities = False
- $23.Name = Fields (Private)
- $23.AllowedPrefixes = $24
- $24.String = m_
- $23.AffectedEntity = Field, ReadonlyField
- $23.VisibilityMask = Private
- $23.NamingStyle = CamelCase
- $23.IncludeInstanceMembers = True
- $23.IncludeStaticEntities = False
- $25.Name = Static Fields (Private)
- $25.AffectedEntity = Field
- $25.VisibilityMask = Private
- $25.NamingStyle = CamelCase
- $25.IncludeInstanceMembers = False
- $25.IncludeStaticEntities = True
- $26.Name = ReadOnly Fields (Private)
- $26.AllowedPrefixes = $27
- $27.String = m_
- $26.AffectedEntity = ReadonlyField
- $26.VisibilityMask = Private
- $26.NamingStyle = CamelCase
- $26.IncludeInstanceMembers = True
- $26.IncludeStaticEntities = False
- $28.Name = Constant Fields
- $28.AffectedEntity = ConstantField
- $28.VisibilityMask = VisibilityMask
- $28.NamingStyle = PascalCase
- $28.IncludeInstanceMembers = True
- $28.IncludeStaticEntities = True
- $29.Name = Properties
- $29.AffectedEntity = Property
- $29.VisibilityMask = VisibilityMask
- $29.NamingStyle = PascalCase
- $29.IncludeInstanceMembers = True
- $29.IncludeStaticEntities = True
- $30.Name = Events
- $30.AffectedEntity = Event
- $30.VisibilityMask = VisibilityMask
- $30.NamingStyle = PascalCase
- $30.IncludeInstanceMembers = True
- $30.IncludeStaticEntities = True
- $31.Name = Enum Members
- $31.AffectedEntity = EnumMember
- $31.VisibilityMask = VisibilityMask
- $31.NamingStyle = PascalCase
- $31.IncludeInstanceMembers = True
- $31.IncludeStaticEntities = True
- $32.Name = Parameters
- $32.AffectedEntity = Parameter
- $32.VisibilityMask = VisibilityMask
- $32.NamingStyle = CamelCase
- $32.IncludeInstanceMembers = True
- $32.IncludeStaticEntities = True
- $33.Name = Type Parameters
- $33.RequiredPrefixes = $34
- $34.String = T
- $33.AffectedEntity = TypeParameter
- $33.VisibilityMask = VisibilityMask
- $33.NamingStyle = PascalCase
- $33.IncludeInstanceMembers = True
- $33.IncludeStaticEntities = True
- $35.inheritsSet = null
- $35.scope = application/x-csproject
- $0.XmlFormattingPolicy = $40
- $36.inheritsSet = null
- $36.scope = application/x-csproject
- $37.inheritsSet = null
- $37.scope = application/x-msbuild
- $38.inheritsSet = null
- $38.scope = application/x-msbuild
- $39.inheritsSet = null
- $39.scope = application/xml
- $40.inheritsSet = Mono
- $40.inheritsScope = application/xml
- $40.scope = application/xml
- $0.VersionControlPolicy = $41
- $41.inheritsSet = Mono
- $0.ChangeLogPolicy = $42
- $42.UpdateMode = None
- $42.MessageStyle = $43
- $43.LineAlign = 0
- $42.inheritsSet = Mono
- version = 1.0.8.0
- outputpath = ..\..\GameData\SCANsat
- EndGlobalSection
-EndGlobal
diff --git a/SCANsat.slnx b/SCANsat.slnx
new file mode 100644
index 000000000..f31b42abf
--- /dev/null
+++ b/SCANsat.slnx
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SCANsat/SCAN_Color_Config.cs b/SCANsat/SCAN_Color_Config.cs
index 97eca5647..8c630e783 100644
--- a/SCANsat/SCAN_Color_Config.cs
+++ b/SCANsat/SCAN_Color_Config.cs
@@ -14,9 +14,6 @@
using System.Collections.Generic;
using SCANsat.SCAN_Data;
using SCANsat.SCAN_Platform;
-using SCANsat.SCAN_Palettes;
-using UnityEngine;
-using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
namespace SCANsat
{
diff --git a/SCANsat/SCAN_Data/SCANdata.cs b/SCANsat/SCAN_Data/SCANdata.cs
index 39715d476..54eae09ac 100644
--- a/SCANsat/SCAN_Data/SCANdata.cs
+++ b/SCANsat/SCAN_Data/SCANdata.cs
@@ -11,22 +11,21 @@
*/
#endregion
-using System;
-using System.Linq;
-using System.Collections.Generic;
-using UnityEngine;
using Contracts;
using FinePrint;
using FinePrint.Contracts;
using FinePrint.Contracts.Parameters;
using FinePrint.Utilities;
-using System.IO;
-using System.Runtime.Serialization.Formatters.Binary;
using SCANsat.SCAN_Platform;
-using SCANsat.SCAN_Palettes;
+using SCANsat.SCAN_Reflection;
using SCANsat.SCAN_Unity;
-using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
-using Log = KSPBuildTools.Log;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization.Formatters.Binary;
+using UnityEngine;
+using Log = KSPCommunityLib.Logging.Log;
namespace SCANsat.SCAN_Data
{
@@ -56,32 +55,6 @@ internal SCANdata(CelestialBody b)
{
built = true;
}
-
- terrainConfig = SCANcontroller.getTerrainNode(b.bodyName);
-
- if (terrainConfig == null)
- {
- float? clamp = null;
- if (b.ocean)
- {
- clamp = 0;
- }
-
- float newMax;
-
- try
- {
- newMax = ((float)CelestialUtilities.GetHighestPeak(b)).Mathf_Round(-2);
- }
- catch (Exception e)
- {
- SCANUtil.SCANlog("Error in calculating Max Height for {0}; using default value\n{1}", b.displayName.LocalizeBodyName(), e);
- newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
- }
-
- terrainConfig = new SCANterrainConfig(SCANconfigLoader.SCANNode.DefaultMinHeightRange, newMax, clamp, SCAN_Palette_Config.DefaultPalette.GetPalette(0), 7, false, false, body);
- SCANcontroller.addToTerrainConfigData(body.bodyName, terrainConfig);
- }
}
public SCANdata(SCANdata copy)
diff --git a/SCANsat/SCAN_Data/SCANresourceGlobal.cs b/SCANsat/SCAN_Data/SCANresourceGlobal.cs
index 50338095c..56db170d2 100644
--- a/SCANsat/SCAN_Data/SCANresourceGlobal.cs
+++ b/SCANsat/SCAN_Data/SCANresourceGlobal.cs
@@ -17,7 +17,7 @@
using UnityEngine;
using SCANsat.SCAN_Platform;
-using Log = KSPBuildTools.Log;
+using Log = KSPCommunityLib.Logging.Log;
namespace SCANsat.SCAN_Data
{
diff --git a/SCANsat/SCAN_Map/SCANmap.cs b/SCANsat/SCAN_Map/SCANmap.cs
index cbf759314..afaa61e87 100644
--- a/SCANsat/SCAN_Map/SCANmap.cs
+++ b/SCANsat/SCAN_Map/SCANmap.cs
@@ -893,7 +893,6 @@ internal Texture2D getPartialMap(bool apply = true)
}
Texture2D readableScaledSpaceMap = SCANcontroller.controller.getVisualMapTexture(body);
- Texture2D readableScaledSpaceNormalMap = SCANcontroller.controller.getVisualMapNormalTexture(body);
for (int i = 0; i < map.width; i++)
{
@@ -1173,120 +1172,53 @@ internal Texture2D getPartialMap(bool apply = true)
break;
}
case mapType.Visual:
- {
- if (readableScaledSpaceMap == null)
- {
- baseColor = palette.lerp(palette.Black, palette.White, UnityEngine.Random.value);
- }
- else if (SCANUtil.isCovered(lon, lat, data, SCANtype.VisualHiRes))
- {
- float fLat = ((float)lat + 90f) / 180f;
- float fLon = ((float)lon + 270f) / 360f;
-
- if (fLon < 0)
- {
- fLon += 1;
- }
+ {
+ bool highResCovered = SCANUtil.isCovered(lon, lat, data, SCANtype.VisualHiRes);
+ bool lowResCovered = SCANUtil.isCovered(lon, lat, data, SCANtype.VisualLoRes);
- if (fLon > 1)
- {
- fLon -= 1;
- }
+ if (readableScaledSpaceMap == null)
+ {
+ baseColor = palette.lerp(palette.Black, palette.White, UnityEngine.Random.value);
+ }
+ else if (highResCovered || lowResCovered)
+ {
+ float fLat = ((float)lat + 90f) / 180f;
+ float fLon = ((float)lon + 270f) / 360f;
- fLon = 1 - fLon;
+ if (fLon < 0) { fLon += 1; }
+ if (fLon > 1) { fLon -= 1; }
+ fLon = 1 - fLon;
- fLat = Mathf.Clamp01(fLat);
- fLon = Mathf.Clamp01(fLon);
+ fLat = Mathf.Clamp01(fLat);
+ fLon = Mathf.Clamp01(fLon);
+ if (highResCovered)
+ {
baseColor = readableScaledSpaceMap.GetPixelBilinear(fLon, fLat);
-
- if (readableScaledSpaceNormalMap != null)
- {
- HslColor hslBase = palette.ConvertRgbToHsl(baseColor);
-
- double opacity = 0.8;
-
- double lumOver = readableScaledSpaceNormalMap.GetPixelBilinear(fLon, fLat).b;
- double lum = hslBase.L;
-
- if (colorMap)
- {
- if (lum > 0.5d)
- {
- lum = (opacity * (1 - (1 - (2 * (lumOver - 0.5))) * (1 - lum))) + (1 - opacity) * lum;
- }
- else
- {
- lum = (opacity * (2 * lumOver * lum)) + (1 - opacity) * lum;
- }
-
- baseColor = palette.ConvertHslToRgb(hslBase.H, hslBase.S, lum);
- }
- else
- {
- if (lum > 0.5d)
- {
- lum = (1 - (1 - (2 * (lumOver - 0.5))) * (1 - lum));
- }
- else
- {
- lum = 2 * lumOver * lum;
- }
-
- baseColor = palette.ConvertToGrayscale(baseColor);
- }
- }
}
- else if (SCANUtil.isCovered(lon, lat, data, SCANtype.VisualLoRes))
+ else
{
- float fLat = ((float)lat + 90f) / 180f;
- float fLon = ((float)lon + 270f) / 360f;
-
- if (fLon < 0)
- {
- fLon += 1;
- }
-
- if (fLon > 1)
- {
- fLon -= 1;
- }
-
- fLon = 1 - fLon;
-
- fLat = Mathf.Clamp01(fLat);
- fLon = Mathf.Clamp01(fLon);
-
- float width = readableScaledSpaceMap.width / (512f);
- float height = readableScaledSpaceMap.height / (256f);
-
- int ilon = Mathf.RoundToInt((int)(fLon * 512f) * width);
- int ilat = Mathf.RoundToInt((int)(fLat * 256f) * height);
-
- if (ilon > readableScaledSpaceMap.width)
- {
- ilon = readableScaledSpaceMap.width - 1;
- }
-
- if (ilat > readableScaledSpaceMap.height)
- {
- ilat = readableScaledSpaceMap.height - 1;
- }
+ int ilon = Mathf.RoundToInt(fLon * readableScaledSpaceMap.width);
+ int ilat = Mathf.RoundToInt(fLat * readableScaledSpaceMap.height);
baseColor = readableScaledSpaceMap.GetPixel(ilon, ilat);
-
- if (!colorMap)
- {
- baseColor = palette.ConvertToGrayscale(baseColor);
- }
}
- else
+
+ // Ensure full opacity for visual maps
+ baseColor.a = 255;
+
+ if (!colorMap)
{
- baseColor = unscanned;
+ baseColor = palette.ConvertToGrayscale(baseColor);
}
-
- break;
}
+ else
+ {
+ baseColor = unscanned;
+ }
+
+ break;
+ }
}
if (resourceOn)
diff --git a/SCANsat/SCAN_Map/SCANmapExporter.cs b/SCANsat/SCAN_Map/SCANmapExporter.cs
index 23eed94b7..e2acc60fa 100644
--- a/SCANsat/SCAN_Map/SCANmapExporter.cs
+++ b/SCANsat/SCAN_Map/SCANmapExporter.cs
@@ -5,7 +5,7 @@
using UnityEngine;
using SCANsat.SCAN_Data;
using SCANsat.SCAN_Platform;
-using Log = KSPBuildTools.Log;
+using Log = KSPCommunityLib.Logging.Log;
namespace SCANsat.SCAN_Map
{
diff --git a/SCANsat/SCAN_Map/SCANmapLegend.cs b/SCANsat/SCAN_Map/SCANmapLegend.cs
index 34bf08f0e..c2e5a156a 100644
--- a/SCANsat/SCAN_Map/SCANmapLegend.cs
+++ b/SCANsat/SCAN_Map/SCANmapLegend.cs
@@ -118,7 +118,7 @@ public Texture2D getLegend(SCANdata data, bool color, bool stock, CBAttributeMap
if (stock && color)
{
- pix[current] = biomes[i].mapColor;
+ pix[current] = SCANUtil.getBiomeDisplayColor(data.Body, i);
}
else if (color)
{
@@ -145,7 +145,7 @@ public Texture2D getLegend(SCANdata data, bool color, bool stock, CBAttributeMap
if (SCAN_Settings_Config.Instance.BigMapStockBiomes && color)
{
- pix[256 - backCount] = biomes[count - 1].mapColor;
+ pix[256 - backCount] = SCANUtil.getBiomeDisplayColor(data.Body, count - 1);
}
else if (color)
{
@@ -194,4 +194,4 @@ public static Texture2D getStaticLegend(float max, float min, float range, float
return t;
}
}
-}
+}
\ No newline at end of file
diff --git a/SCANsat/SCAN_PartModules/SCANsat.cs b/SCANsat/SCAN_PartModules/SCANsat.cs
index ed6e4b3c3..1903be86b 100644
--- a/SCANsat/SCAN_PartModules/SCANsat.cs
+++ b/SCANsat/SCAN_PartModules/SCANsat.cs
@@ -23,7 +23,7 @@
using UnityEngine;
using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
-using Log = KSPBuildTools.Log;
+using Log = KSPCommunityLib.Logging.Log;
namespace SCANsat.SCAN_PartModules
{
diff --git a/SCANsat/SCAN_Platform/Extensions/ConfigNodes/ConfigExtensions.cs b/SCANsat/SCAN_Platform/Extensions/ConfigNodes/ConfigExtensions.cs
index 3cab8e8ca..d84e994b3 100644
--- a/SCANsat/SCAN_Platform/Extensions/ConfigNodes/ConfigExtensions.cs
+++ b/SCANsat/SCAN_Platform/Extensions/ConfigNodes/ConfigExtensions.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
-using Log = KSPBuildTools.Log;
+using Log = KSPCommunityLib.Logging.Log;
namespace SCANsat.SCAN_Platform.Extensions.ConfigNodes
{
diff --git a/SCANsat/SCAN_Platform/Logging.cs b/SCANsat/SCAN_Platform/Logging.cs
index 76295b404..12fe087b2 100644
--- a/SCANsat/SCAN_Platform/Logging.cs
+++ b/SCANsat/SCAN_Platform/Logging.cs
@@ -1,5 +1,5 @@
using System;
-using Log = KSPBuildTools.Log;
+using Log = KSPCommunityLib.Logging.Log;
// TODO: remove this file and fixup references to call KSPBuildTools.Log directly
diff --git a/SCANsat/SCAN_Reflection/SCANkopernicus.cs b/SCANsat/SCAN_Reflection/SCANkopernicus.cs
index 45c5254ce..8c35c382d 100644
--- a/SCANsat/SCAN_Reflection/SCANkopernicus.cs
+++ b/SCANsat/SCAN_Reflection/SCANkopernicus.cs
@@ -15,7 +15,7 @@
using System.Linq;
using System.Reflection;
using UnityEngine;
-using Log = KSPBuildTools.Log;
+using Log = KSPCommunityLib.Logging.Log;
namespace SCANsat.SCAN_Reflection
{
diff --git a/SCANsat/SCAN_Reflection/SCANparallaxContinued.cs b/SCANsat/SCAN_Reflection/SCANparallaxContinued.cs
index 803ef84fe..a517d6eb3 100644
--- a/SCANsat/SCAN_Reflection/SCANparallaxContinued.cs
+++ b/SCANsat/SCAN_Reflection/SCANparallaxContinued.cs
@@ -2,7 +2,7 @@
using System.Linq;
using System.Reflection;
using UnityEngine;
-using Log = KSPBuildTools.Log;
+using Log = KSPCommunityLib.Logging.Log;
namespace SCANsat.SCAN_Reflection
{
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_Color.cs b/SCANsat/SCAN_Unity/SCAN_UI_Color.cs
index 77d5301ed..e4b3e275a 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_Color.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_Color.cs
@@ -690,7 +690,7 @@ public void BiomeApply(Color one, Color two)
SCAN_UI_BigMap.Instance.RefreshMap();
}
- if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && SCAN_UI_MainMap.Instance.MapType)
+ if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && SCAN_UI_MainMap.Instance.MapType == MainMapDisplayMode.Biome)
{
SCAN_UI_MainMap.Instance.resetImages();
}
@@ -727,7 +727,7 @@ public void BiomeDefault()
SCAN_UI_BigMap.Instance.RefreshMap();
}
- if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && SCAN_UI_MainMap.Instance.MapType)
+ if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && SCAN_UI_MainMap.Instance.MapType == MainMapDisplayMode.Biome)
{
SCAN_UI_MainMap.Instance.resetImages();
}
@@ -944,7 +944,7 @@ public void TerrainApply()
SCAN_UI_BigMap.Instance.RefreshMap();
}
- if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && !SCAN_UI_MainMap.Instance.MapType)
+ if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && SCAN_UI_MainMap.Instance.MapType == MainMapDisplayMode.Terrain)
{
SCAN_UI_MainMap.Instance.resetImages();
}
@@ -979,7 +979,7 @@ public void TerrainDefault()
SCAN_UI_BigMap.Instance.RefreshMap();
}
- if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && !SCAN_UI_MainMap.Instance.MapType)
+ if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && SCAN_UI_MainMap.Instance.MapType == MainMapDisplayMode.Terrain)
{
SCAN_UI_MainMap.Instance.resetImages();
}
@@ -1013,7 +1013,7 @@ public void TerrainSaveToConfig()
SCAN_UI_BigMap.Instance.RefreshMap();
}
- if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && !SCAN_UI_MainMap.Instance.MapType)
+ if (SCAN_UI_MainMap.Instance != null && SCAN_UI_MainMap.Instance.IsVisible && SCAN_UI_MainMap.Instance.MapType == MainMapDisplayMode.Terrain)
{
SCAN_UI_MainMap.Instance.resetImages();
}
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
index 5591a1567..ae90859c0 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
@@ -11,17 +11,16 @@
*/
#endregion
-using System;
-using System.Collections.Generic;
-using System.Text;
-using UnityEngine;
+using KSP.UI;
+using SCANsat.SCAN_Data;
using SCANsat.SCAN_Toolbar;
-using SCANsat.Unity.Interfaces;
+using SCANsat.SCAN_UI.UI_Framework;
using SCANsat.Unity;
+using SCANsat.Unity.Interfaces;
using SCANsat.Unity.Unity;
-using SCANsat.SCAN_Data;
-using SCANsat.SCAN_UI.UI_Framework;
-using KSP.UI;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
namespace SCANsat.SCAN_Unity
@@ -183,24 +182,12 @@ public void Update()
sensors = SCANcontroller.controller.activeSensorsOnVessel(v.id, false);
- if (!SCANcontroller.controller.mainMapBiome)
+ if (SCAN_Settings_Config.Instance.MapGenerationSpeed > 1)
{
- if (SCAN_Settings_Config.Instance.MapGenerationSpeed > 1)
- {
- drawPartialMap(sensors, false);
- }
-
- drawPartialMap(sensors, true);
+ drawPartialMap(SCANcontroller.controller.mainMapDisplayMode, false);
}
- else
- {
- if (SCAN_Settings_Config.Instance.MapGenerationSpeed > 1)
- {
- drawBiomeMap(sensors, false);
- }
- drawBiomeMap(sensors, true);
- }
+ drawPartialMap(SCANcontroller.controller.mainMapDisplayMode, true);
lastUpdate++;
@@ -515,12 +502,12 @@ public bool TerminatorToggle
}
}
- public bool MapType
+ public MainMapDisplayMode MapType
{
- get { return SCANcontroller.controller.mainMapBiome; }
+ get { return SCANcontroller.controller.mainMapDisplayMode; }
set
{
- SCANcontroller.controller.mainMapBiome = value;
+ SCANcontroller.controller.mainMapDisplayMode = value;
resetImages();
}
@@ -811,7 +798,7 @@ public string VesselInfo(Guid id)
float lat = (float)SCANUtil.fixLatShift(ves.latitude);
string units = "";
- if (SCANcontroller.controller.mainMapBiome)
+ if (SCANcontroller.controller.mainMapDisplayMode == MainMapDisplayMode.Biome)
{
if (SCANUtil.isCovered(lon, lat, data, SCANtype.Biome))
{
@@ -852,9 +839,15 @@ public string VesselInfo(Guid id)
return string.Format("({0}°,{1}°{2})", lat.ToString("F1"), lon.ToString("F1"), units);
}
- private void drawPartialMap(SCANtype type, bool apply)
+ private void drawPartialMap(MainMapDisplayMode display, bool apply)
{
bool pqsController = data.Body.pqsController != null;
+ bool biomeMap = data.Body.BiomeMap != null;
+
+ if (biomeBuilding && biomeMap && display == MainMapDisplayMode.Biome)
+ {
+ buildBiomeCache();
+ }
if (data.ControllerBuilding || data.OverlayBuilding)
{
@@ -901,30 +894,7 @@ private void drawPartialMap(SCANtype type, bool apply)
continue;
}
- Color32 c = palette.Grey;
- float val = data.HeightMapValue(data.Body.flightGlobalsIndex, ilon, scanline);
- if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.Altimetry))
- {
- if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.AltimetryHiRes))
- {
- c = palette.heightToColor(val, Color, data.TerrainConfig);
- }
- else
- {
- c = palette.heightToColor(val, false, data.TerrainConfig);
- }
- }
- else
- {
- if (scanline % 30 == 0 && ilon % 3 == 0)
- {
- c = palette.White;
- }
- else if (ilon % 30 == 0 && scanline % 3 == 0)
- {
- c = palette.White;
- }
- }
+ Color32 c = getMapPixelColor(display, scanline, ilon);
if (TerminatorToggle)
{
@@ -945,16 +915,6 @@ private void drawPartialMap(SCANtype type, bool apply)
}
}
}
- else
- {
- if (type != SCANtype.Nothing)
- {
- if (!SCANUtil.isCoveredByAll(ilon, scanline, data, type))
- {
- c = palette.lerp(c, palette.Black, 0.5f);
- }
- }
- }
cols_height_map_small[ilon] = c;
}
@@ -982,102 +942,85 @@ private void drawPartialMap(SCANtype type, bool apply)
}
}
- private void drawBiomeMap(SCANtype type, bool apply)
+ private Color32 getMapPixelColor(MainMapDisplayMode display, int scanline, int ilon)
{
+ bool pqsController = data.Body.pqsController != null;
bool biomeMap = data.Body.BiomeMap != null;
-
- if (biomeBuilding && biomeMap)
- {
- buildBiomeCache();
- }
-
- if (scanline == 0 && TerminatorToggle)
+ Color32 c = palette.Grey;
+ switch (display)
{
- double sunLon = data.Body.GetLongitude(Planetarium.fetch.Sun.position, false);
- double sunLat = data.Body.GetLatitude(Planetarium.fetch.Sun.position, false);
-
- sunLatCenter = SCANUtil.fixLatShift(sunLat);
-
- if (sunLatCenter >= 0)
- {
- sunLonCenter = SCANUtil.fixLonShift(sunLon + 90);
- }
- else
- {
- sunLonCenter = SCANUtil.fixLonShift(sunLon - 90);
- }
-
- gamma = Math.Abs(sunLatCenter) < 0.55 ? 100 : Math.Tan(Mathf.Deg2Rad * (90 - Math.Abs(sunLatCenter)));
- }
-
- for (int ilon = 0; ilon < 360; ilon++)
- {
- if (!biomeMap)
- {
- cols_height_map_small[ilon] = palette.lerp(palette.black, palette.white, UnityEngine.Random.value);
- continue;
- }
-
- Color32 c = biomeCache[scanline * 360 + ilon];
- if (!SCANUtil.isCovered(ilon, scanline, data, SCANtype.Biome))
- {
- c = palette.Grey;
- }
-
- if (TerminatorToggle)
- {
- double crossingLat = Math.Atan(gamma * Math.Sin(Mathf.Deg2Rad * (ilon - 180) - Mathf.Deg2Rad * sunLonCenter));
-
- if (sunLatCenter >= 0)
+ case MainMapDisplayMode.Terrain:
+ if (!pqsController)
{
- if (scanline - 90 < crossingLat * Mathf.Rad2Deg)
- {
- c = palette.lerp(c, palette.Black, 0.5f);
- }
+ c = palette.lerp(palette.black, palette.white, UnityEngine.Random.value);
}
- else
+ else if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.Altimetry))
{
- if (scanline - 90 > crossingLat * Mathf.Rad2Deg)
+ float val = data.HeightMapValue(data.Body.flightGlobalsIndex, ilon, scanline);
+ if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.AltimetryHiRes))
{
- c = palette.lerp(c, palette.Black, 0.5f);
+ c = palette.heightToColor(val, Color, data.TerrainConfig);
}
- }
- }
- else
- {
- if (type != SCANtype.Nothing)
- {
- if (!SCANUtil.isCoveredByAll(ilon, scanline, data, type))
+ else
{
- c = palette.lerp(c, palette.Black, 0.5f);
+ c = palette.heightToColor(val, false, data.TerrainConfig);
}
+ return c;
}
- }
+ break;
- cols_height_map_small[ilon] = c;
- }
+ case MainMapDisplayMode.Biome:
+ if (!biomeMap)
+ {
+ c = palette.lerp(palette.black, palette.white, UnityEngine.Random.value);
+ }
+ else if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.Biome))
+ {
+ c = biomeCache[scanline * 360 + ilon];
+ return c;
+ }
+ break;
- map_small.SetPixels32(0, scanline, 360, 1, cols_height_map_small);
+ case MainMapDisplayMode.Visual:
+ bool highResCovered = SCANUtil.isCovered(ilon, scanline, data, SCANtype.VisualHiRes);
+ bool lowResCovered = SCANUtil.isCovered(ilon, scanline, data, SCANtype.VisualLoRes);
+ if (highResCovered || lowResCovered)
+ {
+ double lat = scanline - 90;
+ double lon = ilon - 180;
- if (apply)
- {
- if (scanline < 179)
- {
- map_small.SetPixels32(0, scanline + 1, 360, 1, palette.small_redline);
- }
- }
+ float fLat = ((float)lat + 90f) / 180f;
+ float fLon = ((float)lon + 270f) / 360f;
- scanline++;
+ if (fLon < 0) { fLon += 1; }
+ if (fLon > 1) { fLon -= 1; }
+ fLon = 1 - fLon;
- if (apply || scanline >= 180)
- {
- map_small.Apply();
+ fLat = Mathf.Clamp01(fLat);
+ fLon = Mathf.Clamp01(fLon);
+
+ Texture2D readableScaledSpaceMap = SCANcontroller.controller.getVisualMapTexture(data.Body);
+
+ int ilon_tex = Mathf.RoundToInt(fLon * readableScaledSpaceMap.width);
+ int ilat_tex = Mathf.RoundToInt(fLat * readableScaledSpaceMap.height);
+
+ c = readableScaledSpaceMap.GetPixel(ilon_tex, ilat_tex);
+ c.a = 255;
+
+ return c;
+ }
+ break;
}
- if (scanline >= 180)
+ if (scanline % 30 == 0 && ilon % 3 == 0)
{
- scanline = 0;
+ c = palette.White;
+ }
+ else if (ilon % 30 == 0 && scanline % 3 == 0)
+ {
+ c = palette.White;
}
+ return c;
}
private void buildBiomeCache()
@@ -1130,7 +1073,7 @@ internal void resetImages()
map_small.Apply();
- if (SCANcontroller.controller.mainMapBiome)
+ if (SCANcontroller.controller.mainMapDisplayMode == MainMapDisplayMode.Biome)
{
biomeBuilding = true;
scanline = 0;
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_Overlay.cs b/SCANsat/SCAN_Unity/SCAN_UI_Overlay.cs
index f1b089b97..bd0c5dc73 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_Overlay.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_Overlay.cs
@@ -26,7 +26,7 @@
using SCANsat.SCAN_UI.UI_Framework;
using KSP.UI;
using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
-using Log = KSPBuildTools.Log;
+using Log = KSPCommunityLib.Logging.Log;
namespace SCANsat.SCAN_Unity
{
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index 0f8f42438..33a7f9334 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -12,28 +12,32 @@
*/
#endregion
-using System;
-using System.Linq;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using UnityEngine;
-using UnityEngine.Events;
using Contracts;
using FinePrint.Contracts;
+using FinePrint.Contracts.Parameters;
using FinePrint.Utilities;
-using SCANsat.SCAN_UI.UI_Framework;
-using SCANsat.SCAN_Unity;
using SCANsat.SCAN_Data;
using SCANsat.SCAN_Map;
-using SCANsat.SCAN_Platform.Extensions.ConfigNodes;
using SCANsat.SCAN_Palettes;
+using SCANsat.SCAN_Platform.Extensions.ConfigNodes;
+using SCANsat.SCAN_Reflection;
using SCANsat.SCAN_Toolbar;
-using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
-using FinePrint.Contracts.Parameters;
+using SCANsat.SCAN_UI.UI_Framework;
+using SCANsat.SCAN_Unity;
+using SCANsat.Unity.Interfaces;
+using SoftMasking.Extensions;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+using UnityEngine.Events;
using UnityEngine.Profiling;
-using Log = KSPBuildTools.Log;
-using SCANsat.SCAN_Reflection;
+using Log = KSPCommunityLib.Logging.Log;
+using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
namespace SCANsat
{
@@ -58,7 +62,7 @@ public static SCANcontroller controller
[KSPField(isPersistant = true)]
public bool mainMapTerminator = false;
[KSPField(isPersistant = true)]
- public bool mainMapBiome = false;
+ public MainMapDisplayMode mainMapDisplayMode = MainMapDisplayMode.Terrain;
[KSPField(isPersistant = true)]
public bool mainMapMinimized = false;
[KSPField(isPersistant = true)]
@@ -155,7 +159,6 @@ public static SCANcontroller controller
/* Visual Map Texture Data */
private Dictionary readableScaledSpaceMaps = new Dictionary();
- private Dictionary readableScaledSpaceNormalMaps = new Dictionary();
private CelestialBody bigMapBodyScaledSpace;
private CelestialBody zoomMapBodyScaledSpace;
@@ -249,16 +252,6 @@ public Texture2D getVisualMapTexture(CelestialBody b)
return null;
}
- public Texture2D getVisualMapNormalTexture(CelestialBody b)
- {
- if (readableScaledSpaceNormalMaps.ContainsKey(b))
- {
- return readableScaledSpaceNormalMaps[b];
- }
-
- return null;
- }
-
public static List EncodeTerrainConfigs
{
get
@@ -300,30 +293,32 @@ public static void checkLoadedTerrainNodes()
continue;
}
- if (getTerrainNode(b.bodyName) == null)
+ float? clamp = null;
+ if (b.ocean)
{
- float? clamp = null;
- if (b.ocean)
- {
- clamp = 0;
- }
+ clamp = 0;
+ }
- float newMax;
+ float newMin;
+ float newMax;
- try
- {
- newMax = ((float)CelestialUtilities.GetHighestPeak(b)).Mathf_Round(-2);
- }
- catch (Exception e)
+ try
+ {
+ newMin = ((float)(b.pqsController.radiusMin - b.pqsController.radius)).Mathf_Round(-1);
+ newMax = ((float)(b.pqsController.radiusMax - b.pqsController.radius)).Mathf_Round(-1);
+ if (newMin == newMax)
{
- SCANUtil.SCANlog("Error in calculating Max Height for {0}; using default value/n{1}", b.bodyName, e);
- newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
+ throw new Exception("Gas Giant / Flat Body"); // Clamp altimetry if body is perfectly smooth / gas giant
}
-
- SCANUtil.SCANlog("Generating new SCANsat Terrain Config for [{0}] - Max Height: [{1:F0}m]", b.bodyName, newMax);
-
- addToTerrainConfigData(b.bodyName, new SCANterrainConfig(SCANconfigLoader.SCANNode.DefaultMinHeightRange, newMax, clamp, SCANUtil.PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b));
}
+ catch (Exception e)
+ {
+ SCANUtil.SCANlog($"[{b.name}] Error in calculating Max Height; using default value\n{e}");
+ newMin = SCANconfigLoader.SCANNode.DefaultMinHeightRange;
+ newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
+ }
+ SCANUtil.SCANlog($"[{b.bodyName}] Terrain Config Generation: Heights between {newMin:F0}m and {newMax:F0}m. Clamp is {clamp}.");
+ addToTerrainConfigData(b.bodyName, new SCANterrainConfig(newMin, newMax, clamp, SCANUtil.PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b));
}
}
@@ -358,14 +353,14 @@ public static void updateTerrainConfig(SCANterrainConfig t)
public static void addToTerrainConfigData(string name, SCANterrainConfig data)
{
- if (!masterTerrainNodes.ContainsKey(name))
- {
- masterTerrainNodes.Add(name, data);
- }
- else
+ if (masterTerrainNodes.ContainsKey(name))
{
- Log.Error("SCANterrain Data Dictionary Already Contains Key Of This Type");
+ Log.Warning($"[{name}] Terrain Config already stored in SCANterrain Data Dictionary");
+ updateTerrainConfig(data);
+ return;
}
+
+ masterTerrainNodes.Add(name, data);
}
public static int MasterResourceCount
@@ -721,41 +716,7 @@ public override void OnLoad(ConfigNode node)
data.Disabled = node_body.parse("Disabled", false);
- float min = node_body.parse("MinHeightRange", data.TerrainConfig.DefaultMinHeight);
- float max = node_body.parse("MaxHeightRange", data.TerrainConfig.DefaultMaxHeight);
- float? clampState = node_body.parse("ClampHeight", (float?)null);
-
- int pSize = node_body.parse("PaletteSize", data.TerrainConfig.DefaultPaletteSize);
- bool pRev = node_body.parse("PaletteReverse", data.TerrainConfig.DefaultReverse);
- bool pDis = node_body.parse("PaletteDiscrete", data.TerrainConfig.DefaultDiscrete);
-
- string paletteName = node_body.parse("PaletteName", "");
-
- if (string.IsNullOrEmpty(paletteName))
- {
- paletteName = data.TerrainConfig.DefaultPalette.Name;
- }
-
- SCANPalette dataPalette = SCANUtil.PaletteLoader(paletteName, pSize);
-
- if (dataPalette.Hash == SCAN_Palette_Config.DefaultPalette.GetPalette(0).Hash)
- {
- paletteName = "Default";
- pSize = 7;
- }
-
- SCANterrainConfig dataTerrainConfig = getTerrainNode(body.bodyName);
-
- if (dataTerrainConfig == null)
- {
- dataTerrainConfig = new SCANterrainConfig(min, max, clampState, dataPalette, pSize, pRev, pDis, body);
- }
- else
- {
- setNewTerrainConfigValues(dataTerrainConfig, min, max, clampState, dataPalette, pSize, pRev, pDis);
- }
-
- data.TerrainConfig = dataTerrainConfig;
+ data.TerrainConfig = getTerrainNode(body.bodyName);
}
catch (Exception e)
{
@@ -1600,11 +1561,10 @@ internal void LoadVisualMapTexture(CelestialBody b, mapSource s)
}
- void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName, out string normalMapTextureName)
+ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName)
{
material = null;
colorMapTextureName = null;
- normalMapTextureName = null;
useMaterialForColorMap = true;
if (b.scaledBody == null)
@@ -1625,15 +1585,14 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
if (shaderName == "Terrain/Gas Giant")
{
colorMapTextureName = "_DetailCloudPatternTexture";
- normalMapTextureName = "_NormalMap";
return;
}
- else if (shaderName.Contains("ParallaxScaled"))
+ // HapkeScaled is the Sol shader which is also a Parallax-dependent instance
+ else if (shaderName.Contains("ParallaxScaled") || shaderName.Contains("HapkeScaled"))
{
SCANparallaxContinued.LoadParallax(b, ref material);
useMaterialForColorMap = false;
colorMapTextureName = "_ColorMap";
- normalMapTextureName = null; // for whatever reason, the logic in ScANmap that uses the normal map doesn't work with parallax's normal maps
return;
}
else if (material.HasProperty("_MainTex"))
@@ -1644,32 +1603,40 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
{
colorMapTextureName = "_ColorMap";
}
+ }
- if (material.HasProperty("_BumpMap"))
- {
- normalMapTextureName = "_BumpMap";
- }
- else if (material.HasProperty("_NormalMap"))
+ /// Caches visual textures (colour map)
+ void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextureName, bool useMaterialForColorMap)
+ {
+ if (readableScaledSpaceMaps.GetValueOrDefault(b) != null)
{
- normalMapTextureName = "_NormalMap";
+ return; // Already cached
}
- }
- void CacheScaledSpaceTexture(Dictionary cache, CelestialBody b, Material material, string textureName, bool useMaterial)
- {
- if (cache.GetValueOrDefault(b) == null && textureName != null)
+ Texture2D colorMap = null;
+
+ try
{
- var sourceTexture = material.GetTexture(textureName) as Texture2D;
- if (sourceTexture == null)
- {
- Log.Error($"GetTexture returned a null texture for body {b.name}, material {material.name} and texture name {textureName}");
- }
- else
+ // Load color map
+ if (colorMapTextureName != null)
{
- var colorMap = sourceTexture.isReadable ? sourceTexture : readableTexture(sourceTexture, useMaterial ? material : null);
- cache.Add(b, colorMap);
+ var sourceColorTexture = material.GetTexture(colorMapTextureName) as Texture2D;
+ if (sourceColorTexture != null)
+ {
+ colorMap = sourceColorTexture.isReadable ? sourceColorTexture : readableTexture(sourceColorTexture, useMaterialForColorMap ? material : null);
+ readableScaledSpaceMaps.Add(b, colorMap);
+ }
+ else
+ {
+ Log.Error($"GetTexture returned a null texture for body {b.name}, material {material.name} and texture name {colorMapTextureName}");
+ return;
+ }
}
}
+ catch (Exception e)
+ {
+ Log.Error($"Error caching visual texture with luminance for body {b.name}: {e}");
+ }
}
internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
@@ -1684,7 +1651,7 @@ internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
return;
}
- GetVisualMapTexturesForBody(b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName, out string normalMapTextureName);
+ GetVisualMapTexturesForBody(b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName);
if (material == null)
{
@@ -1692,8 +1659,8 @@ internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
}
else
{
- CacheScaledSpaceTexture(readableScaledSpaceMaps, b, material, colorMapTextureName, useMaterialForColorMap);
- CacheScaledSpaceTexture(readableScaledSpaceNormalMaps, b, material, normalMapTextureName, false);
+ // Load color map
+ CacheVisualTexture(b, material, colorMapTextureName, useMaterialForColorMap);
}
switch (s)
@@ -1747,13 +1714,6 @@ internal void UnloadVisualMapTexture(CelestialBody b, mapSource s)
readableScaledSpaceMaps[b] = null;
readableScaledSpaceMaps.Remove(b);
}
-
- if (readableScaledSpaceNormalMaps.ContainsKey(b))
- {
- GameObject.Destroy(readableScaledSpaceNormalMaps[b]);
- readableScaledSpaceNormalMaps[b] = null;
- readableScaledSpaceNormalMaps.Remove(b);
- }
}
private Texture2D readableTexture(Texture tex, Material mat)
diff --git a/SCANsat/SCANmainMenuLoader.cs b/SCANsat/SCANmainMenuLoader.cs
index 59b60941a..e8e1c0619 100644
--- a/SCANsat/SCANmainMenuLoader.cs
+++ b/SCANsat/SCANmainMenuLoader.cs
@@ -17,7 +17,7 @@
using System.Diagnostics;
using System.Linq;
using UnityEngine;
-using Log = KSPBuildTools.Log;
+using Log = KSPCommunityLib.Logging.Log;
using SCANsat.SCAN_Reflection;
namespace SCANsat
diff --git a/SCANsat/SCANsat.csproj b/SCANsat/SCANsat.csproj
index 3350596db..3ef2d95f6 100644
--- a/SCANsat/SCANsat.csproj
+++ b/SCANsat/SCANsat.csproj
@@ -6,19 +6,20 @@
net4.8
- true
- true
+ true
+ true
-
- $(RepoRootPath)\GameData\SCANsat\SCANsat.version
+
+ $(KSPBT_ModRoot)/SCANsat/SCANsat.version
1.12.3
-
+
+
-
+
\ No newline at end of file
diff --git a/SCANsat/SCANutil.cs b/SCANsat/SCANutil.cs
index b03926ed1..2a524e200 100644
--- a/SCANsat/SCANutil.cs
+++ b/SCANsat/SCANutil.cs
@@ -27,6 +27,7 @@
using SCANsat.SCAN_UI.UI_Framework;
using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
using SCANsat.SCAN_Unity;
+using Log = KSPCommunityLib.Logging.Log;
namespace SCANsat
{
@@ -535,6 +536,10 @@ public static bool scanTypeValid(SCANtype type)
public static double[] cosLookUp = new double[180];
public static DictionaryValueList localizedBodyNames = new DictionaryValueList();
+ /* Biome Map Encoding Data */
+ private static Dictionary _scanBiomePaletteCache
+ = new Dictionary();
+
internal static bool isCovered(double lon, double lat, SCANdata data, SCANtype type)
{
int ilon = icLON(lon);
@@ -834,6 +839,48 @@ internal static float ResourceOverlay(double lat, double lon, string name, Celes
return amount;
}
+ private static bool IsGrayscaleEncoded(CBAttributeMapSO.MapAttribute[] attrs)
+ {
+ foreach (var a in attrs)
+ {
+ if (a == null) continue;
+ if (Mathf.Abs(a.mapColor.g - 1f) > 0.01f) return false;
+ if (Mathf.Abs(a.mapColor.b - 1f) > 0.01f) return false;
+ }
+ return true;
+ }
+
+ private static CBAttributeMapSO.MapAttribute[] GetOrBuildPalette(CelestialBody body)
+ {
+ if (_scanBiomePaletteCache.TryGetValue(body.name, out var cached))
+ return cached;
+
+ var src = body.BiomeMap.Attributes;
+ if (!IsGrayscaleEncoded(src))
+ {
+ // Pack ships proper RGBA mapColors. Cache a null sentinel so we
+ // never re-detect, and never substitute.
+ _scanBiomePaletteCache[body.name] = null;
+ return null;
+ }
+
+ // Build replacements with categorical hues via golden-ratio HSV
+ var replacements = new CBAttributeMapSO.MapAttribute[src.Length];
+ for (int i = 0; i < src.Length; i++)
+ {
+ if (src[i] == null) continue;
+ var clone = new CBAttributeMapSO.MapAttribute
+ {
+ name = src[i].name,
+ value = src[i].value,
+ mapColor = Color.HSVToRGB((i * 0.61803398875f) % 1f, 1f, 1f)
+ };
+ replacements[i] = clone;
+ }
+ _scanBiomePaletteCache[body.name] = replacements;
+ return replacements;
+ }
+
private static int getBiomeIndex(CelestialBody body, double lon, double lat)
{
if (body.BiomeMap == null)
@@ -850,6 +897,7 @@ private static int getBiomeIndex(CelestialBody body, double lon, double lat)
}
CBAttributeMapSO.MapAttribute att = body.BiomeMap.GetAtt(Mathf.Deg2Rad * lat, Mathf.Deg2Rad * lon);
+
for (int i = 0; i < body.BiomeMap.Attributes.Length; ++i)
{
if (body.BiomeMap.Attributes[i] == att)
@@ -883,6 +931,10 @@ internal static CBAttributeMapSO.MapAttribute getBiome(CelestialBody body, doubl
return null;
}
+ var palette = GetOrBuildPalette(body);
+ if (palette != null && i < palette.Length && palette[i] != null)
+ return palette[i];
+
return body.BiomeMap.Attributes[i];
}
@@ -929,6 +981,19 @@ internal static void getBiomeDisplayName(StringBuilder sb, CelestialBody body, d
sb.Append(string.IsNullOrEmpty(a.displayname) ? a.name : Localizer.Format(a.displayname));
}
+ internal static Color getBiomeDisplayColor(CelestialBody body, int idx)
+ {
+ if (body == null || body.BiomeMap == null)
+ return Color.gray;
+ if (idx < 0 || idx >= body.BiomeMap.Attributes.Length)
+ return Color.gray;
+
+ var palette = GetOrBuildPalette(body);
+ if (palette != null && palette[idx] != null)
+ return palette[idx].mapColor;
+
+ return body.BiomeMap.Attributes[idx].mapColor;
+ }
internal static int countBits(int i)
{
@@ -1342,13 +1407,13 @@ public void ToDMS(StringBuilder sb)
internal static void SCANlog(string log, params object[] stringObjects)
{
- KSPBuildTools.Log.Message(string.Format(log, stringObjects));
+ Log.Message(string.Format(log, stringObjects));
}
[System.Diagnostics.Conditional("DEBUG")]
internal static void SCANdebugLog(string log, params object[] stringObjects)
{
- KSPBuildTools.Log.Debug(string.Format(log, stringObjects));
+ Log.Debug(string.Format(log, stringObjects));
}
#endregion
@@ -1591,4 +1656,3 @@ public static bool IsInIVA()
}
}
#endregion
-
From 17d3e5e418fad95fbd9bf603011f28efc75af2b9 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Thu, 28 May 2026 11:27:14 +1200
Subject: [PATCH 02/20] Prevent new saves without terrain configs loaded from
freezing
---
GameData/SCANsat/SCANsat/SCANsat.version | 7 ++++++
SCANsat.Unity/InputHandler.cs | 4 ----
SCANsat/SCAN_Data/SCANdata.cs | 2 --
SCANsat/SCANcontroller.cs | 27 +++++++++++++-----------
4 files changed, 22 insertions(+), 18 deletions(-)
create mode 100644 GameData/SCANsat/SCANsat/SCANsat.version
diff --git a/GameData/SCANsat/SCANsat/SCANsat.version b/GameData/SCANsat/SCANsat/SCANsat.version
new file mode 100644
index 000000000..2b4319d5c
--- /dev/null
+++ b/GameData/SCANsat/SCANsat/SCANsat.version
@@ -0,0 +1,7 @@
+{
+ "NAME": "SCANsat",
+ "VERSION": "21.1.0.0",
+ "KSP_VERSION": "1.12",
+ "KSP_VERSION_MIN": "1.12.3",
+ "KSP_VERSION_MAX": "1.12"
+}
diff --git a/SCANsat.Unity/InputHandler.cs b/SCANsat.Unity/InputHandler.cs
index 291a7746c..00cbcba6b 100644
--- a/SCANsat.Unity/InputHandler.cs
+++ b/SCANsat.Unity/InputHandler.cs
@@ -11,12 +11,8 @@
*/
#endregion
-using System;
-using System.Collections.Generic;
using UnityEngine;
-using UnityEngine.UI;
using UnityEngine.Events;
-using UnityEngine.EventSystems;
namespace SCANsat.Unity
{
diff --git a/SCANsat/SCAN_Data/SCANdata.cs b/SCANsat/SCAN_Data/SCANdata.cs
index 54eae09ac..3fa53cd1e 100644
--- a/SCANsat/SCAN_Data/SCANdata.cs
+++ b/SCANsat/SCAN_Data/SCANdata.cs
@@ -15,9 +15,7 @@
using FinePrint;
using FinePrint.Contracts;
using FinePrint.Contracts.Parameters;
-using FinePrint.Utilities;
using SCANsat.SCAN_Platform;
-using SCANsat.SCAN_Reflection;
using SCANsat.SCAN_Unity;
using System;
using System.Collections.Generic;
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index 33a7f9334..fc27e622c 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -317,8 +317,9 @@ public static void checkLoadedTerrainNodes()
newMin = SCANconfigLoader.SCANNode.DefaultMinHeightRange;
newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
}
- SCANUtil.SCANlog($"[{b.bodyName}] Terrain Config Generation: Heights between {newMin:F0}m and {newMax:F0}m. Clamp is {clamp}.");
- addToTerrainConfigData(b.bodyName, new SCANterrainConfig(newMin, newMax, clamp, SCANUtil.PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b));
+
+ SCANUtil.SCANlog($"[{b.bodyName}] Terrain Config Generation: Heights between {newMin:F0}m and {newMax:F0}m. Clamp is {clamp}.");
+ addToTerrainConfigData(b.bodyName, new SCANterrainConfig(newMin, newMax, clamp, SCANUtil.PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b));
}
}
@@ -715,7 +716,6 @@ public override void OnLoad(ConfigNode node)
}
data.Disabled = node_body.parse("Disabled", false);
-
data.TerrainConfig = getTerrainNode(body.bodyName);
}
catch (Exception e)
@@ -787,17 +787,20 @@ public override void OnSave(ConfigNode node)
node_body.AddValue("LandingTarget", string.Format("{0:N4},{1:N4}", w.Latitude, w.Longitude));
}
}
- node_body.AddValue("MinHeightRange", body_scan.TerrainConfig.MinTerrain / body_scan.TerrainConfig.MinHeightMultiplier);
- node_body.AddValue("MaxHeightRange", body_scan.TerrainConfig.MaxTerrain / body_scan.TerrainConfig.MaxHeightMultiplier);
- if (body_scan.TerrainConfig.ClampTerrain != null)
+ if (body_scan.TerrainConfig != null)
{
- node_body.AddValue("ClampHeight", body_scan.TerrainConfig.ClampTerrain / body_scan.TerrainConfig.ClampHeightMultiplier);
- }
+ node_body.AddValue("MinHeightRange", body_scan.TerrainConfig.MinTerrain / body_scan.TerrainConfig.MinHeightMultiplier);
+ node_body.AddValue("MaxHeightRange", body_scan.TerrainConfig.MaxTerrain / body_scan.TerrainConfig.MaxHeightMultiplier);
+ if (body_scan.TerrainConfig.ClampTerrain != null)
+ {
+ node_body.AddValue("ClampHeight", body_scan.TerrainConfig.ClampTerrain / body_scan.TerrainConfig.ClampHeightMultiplier);
+ }
- node_body.AddValue("PaletteName", body_scan.TerrainConfig.ColorPal.Name);
- node_body.AddValue("PaletteSize", body_scan.TerrainConfig.PalSize);
- node_body.AddValue("PaletteReverse", body_scan.TerrainConfig.PalRev);
- node_body.AddValue("PaletteDiscrete", body_scan.TerrainConfig.PalDis);
+ node_body.AddValue("PaletteName", body_scan.TerrainConfig.ColorPal.Name);
+ node_body.AddValue("PaletteSize", body_scan.TerrainConfig.PalSize);
+ node_body.AddValue("PaletteReverse", body_scan.TerrainConfig.PalRev);
+ node_body.AddValue("PaletteDiscrete", body_scan.TerrainConfig.PalDis);
+ }
node_body.AddValue("Map", body_scan.shortSerialize());
node_progress.AddNode(node_body);
}
From 6b43a61059d0fa2abe9e34b3ea9b7305abd915d5 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Thu, 28 May 2026 12:23:36 +1200
Subject: [PATCH 03/20] Add debug prints to clarify whether a _ColorMap or
_ColorCube was being loaded in when using Parallax
---
SCANsat/SCANcontroller.cs | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index fc27e622c..867ae36d6 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -1584,6 +1584,7 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
material = scaledMesh.sharedMaterial; // TODO: what if there are multiple materials? do we need to check all of them?
string shaderName = material.shader.name;
+ SCANUtil.SCANdebugLog($"[{b.name}] Material loaded {shaderName}");
if (shaderName == "Terrain/Gas Giant")
{
@@ -1595,7 +1596,11 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
{
SCANparallaxContinued.LoadParallax(b, ref material);
useMaterialForColorMap = false;
- colorMapTextureName = "_ColorMap";
+ string contains_main = material.HasProperty("_MainTex") ? " _MainTex" : null;
+ string contains_map = material.HasProperty("_ColorMap") ? "_ColorMap" : null;
+ string contains_cube = material.HasProperty("_ColorCube") ? "_ColorCube" : null;
+ SCANUtil.SCANdebugLog($"[{b.name}] Material uses Parallax and contains: {contains_main}, {contains_map}, {contains_cube}.");
+ colorMapTextureName = contains_cube != null ? contains_cube : "_ColorMap";
return;
}
else if (material.HasProperty("_MainTex"))
@@ -1627,6 +1632,7 @@ void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextu
if (sourceColorTexture != null)
{
colorMap = sourceColorTexture.isReadable ? sourceColorTexture : readableTexture(sourceColorTexture, useMaterialForColorMap ? material : null);
+ SCANUtil.SCANdebugLog($"[{b.name}] {colorMapTextureName} Texture Size is {colorMap.width} x {colorMap.height}.");
readableScaledSpaceMaps.Add(b, colorMap);
}
else
From 02df5a1449c28e8d03b31a2640f7ee908c5f4d55 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Sun, 31 May 2026 10:15:57 +1200
Subject: [PATCH 04/20] Use .cfg files to define custom SCANsat textures and
memory map them in to reduce RAM usage. Something still goes wrong with
altimetry on first load - TerrainConfig isn't correct until map switch.
---
SCANsat/SCAN_Map/SCANmap.cs | 48 ++-----
SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs | 32 ++---
SCANsat/SCANcontroller.cs | 198 +++++++++++++++++++-------
SCANsat/SCANsat.csproj | 7 +
4 files changed, 180 insertions(+), 105 deletions(-)
diff --git a/SCANsat/SCAN_Map/SCANmap.cs b/SCANsat/SCAN_Map/SCANmap.cs
index afaa61e87..9972d602c 100644
--- a/SCANsat/SCAN_Map/SCANmap.cs
+++ b/SCANsat/SCAN_Map/SCANmap.cs
@@ -892,8 +892,6 @@ internal Texture2D getPartialMap(bool apply = true)
}
}
- Texture2D readableScaledSpaceMap = SCANcontroller.controller.getVisualMapTexture(body);
-
for (int i = 0; i < map.width; i++)
{
/* Introduce altimetry check here; Use unprojected lat/long coordinates
@@ -905,7 +903,7 @@ internal Texture2D getPartialMap(bool apply = true)
double cacheLat = ((mapstep + 1) * 1.0f / mapscale) - 90f + lat_offset;
double lon = (i * 1.0f / mapscale) - 180f + lon_offset;
- if (mType != mapType.Visual)
+ if (mType == mapType.Altimetry || mType == mapType.Slope)
{
if (body.pqsController != null && cache && mapstep + 1 < map.height)
{
@@ -1173,49 +1171,31 @@ internal Texture2D getPartialMap(bool apply = true)
}
case mapType.Visual:
{
- bool highResCovered = SCANUtil.isCovered(lon, lat, data, SCANtype.VisualHiRes);
- bool lowResCovered = SCANUtil.isCovered(lon, lat, data, SCANtype.VisualLoRes);
-
- if (readableScaledSpaceMap == null)
+ if (!SCANcontroller.controller.isVisualTextureLoaded(body))
{
- baseColor = palette.lerp(palette.Black, palette.White, UnityEngine.Random.value);
+ baseColor = unscanned;
+ break;
}
- else if (highResCovered || lowResCovered)
- {
- float fLat = ((float)lat + 90f) / 180f;
- float fLon = ((float)lon + 270f) / 360f;
-
- if (fLon < 0) { fLon += 1; }
- if (fLon > 1) { fLon -= 1; }
- fLon = 1 - fLon;
- fLat = Mathf.Clamp01(fLat);
- fLon = Mathf.Clamp01(fLon);
+ bool highResCovered = SCANUtil.isCovered(lon, lat, data, SCANtype.VisualHiRes);
+ bool lowResCovered = SCANUtil.isCovered(lon, lat, data, SCANtype.VisualLoRes);
- if (highResCovered)
- {
- baseColor = readableScaledSpaceMap.GetPixelBilinear(fLon, fLat);
- }
- else
+ if (highResCovered || lowResCovered)
+ {
+ if (!highResCovered)
{
- int ilon = Mathf.RoundToInt(fLon * readableScaledSpaceMap.width);
- int ilat = Mathf.RoundToInt(fLat * readableScaledSpaceMap.height);
-
- baseColor = readableScaledSpaceMap.GetPixel(ilon, ilat);
+ // Scale data to create a 512 x 256 map of blocky, low-res pixels
+ lon = Mathf.RoundToInt((float)lon * 512) / 512;
+ lat = Mathf.RoundToInt((float)lat * 256) / 256;
}
- // Ensure full opacity for visual maps
- baseColor.a = 255;
+ baseColor = SCANcontroller.controller.GetVisualPixel(body, lon, lat);
- if (!colorMap)
+ if (!colorMap || !highResCovered)
{
baseColor = palette.ConvertToGrayscale(baseColor);
}
}
- else
- {
- baseColor = unscanned;
- }
break;
}
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
index ae90859c0..317c30459 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
@@ -982,30 +982,30 @@ private Color32 getMapPixelColor(MainMapDisplayMode display, int scanline, int i
break;
case MainMapDisplayMode.Visual:
+
+ if (!SCANcontroller.controller.isVisualTextureLoaded(data.Body))
+ {
+ c = palette.lerp(palette.black, palette.white, UnityEngine.Random.value);
+ break;
+ }
+
bool highResCovered = SCANUtil.isCovered(ilon, scanline, data, SCANtype.VisualHiRes);
bool lowResCovered = SCANUtil.isCovered(ilon, scanline, data, SCANtype.VisualLoRes);
+
if (highResCovered || lowResCovered)
{
double lat = scanline - 90;
double lon = ilon - 180;
- float fLat = ((float)lat + 90f) / 180f;
- float fLon = ((float)lon + 270f) / 360f;
-
- if (fLon < 0) { fLon += 1; }
- if (fLon > 1) { fLon -= 1; }
- fLon = 1 - fLon;
-
- fLat = Mathf.Clamp01(fLat);
- fLon = Mathf.Clamp01(fLon);
-
- Texture2D readableScaledSpaceMap = SCANcontroller.controller.getVisualMapTexture(data.Body);
-
- int ilon_tex = Mathf.RoundToInt(fLon * readableScaledSpaceMap.width);
- int ilat_tex = Mathf.RoundToInt(fLat * readableScaledSpaceMap.height);
+ if (highResCovered || lowResCovered)
+ {
+ c = SCANcontroller.controller.GetVisualPixel(data.Body, lon, lat);
- c = readableScaledSpaceMap.GetPixel(ilon_tex, ilat_tex);
- c.a = 255;
+ if (!highResCovered)
+ {
+ c = palette.ConvertToGrayscale(c); // Default to grayscale for low res
+ }
+ }
return c;
}
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index 867ae36d6..b5624e184 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -16,6 +16,8 @@
using FinePrint.Contracts;
using FinePrint.Contracts.Parameters;
using FinePrint.Utilities;
+using KSPCommunityLib.Logging;
+using KSPTextureLoader;
using SCANsat.SCAN_Data;
using SCANsat.SCAN_Map;
using SCANsat.SCAN_Palettes;
@@ -25,17 +27,12 @@
using SCANsat.SCAN_UI.UI_Framework;
using SCANsat.SCAN_Unity;
using SCANsat.Unity.Interfaces;
-using SoftMasking.Extensions;
using System;
using System.Collections;
using System.Collections.Generic;
-using System.ComponentModel;
-using System.IO;
using System.Linq;
-using System.Text;
using UnityEngine;
using UnityEngine.Events;
-using UnityEngine.Profiling;
using Log = KSPCommunityLib.Logging.Log;
using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
@@ -159,6 +156,7 @@ public static SCANcontroller controller
/* Visual Map Texture Data */
private Dictionary readableScaledSpaceMaps = new Dictionary();
+ private Dictionary mappableScaledSpaceMaps = new Dictionary();
private CelestialBody bigMapBodyScaledSpace;
private CelestialBody zoomMapBodyScaledSpace;
@@ -237,19 +235,58 @@ public void addToBodyData(CelestialBody b, SCANdata data)
}
}
- public Texture2D getVisualMapTexture(CelestialBody b)
+ public bool isVisualTextureLoaded(CelestialBody b)
{
if (!SCAN_Settings_Config.Instance.VisibleMapsActive)
{
- return null;
+ return false;
}
- if (readableScaledSpaceMaps.ContainsKey(b))
+ // Check if a texture is present in either dictionary
+ if (readableScaledSpaceMaps.GetValueOrDefault(b) != null || mappableScaledSpaceMaps.GetValueOrDefault(b) != null)
{
- return readableScaledSpaceMaps[b];
+ return true;
}
- return null;
+ return false;
+ }
+
+ public Color32 GetVisualPixel(CelestialBody b, double lon, double lat)
+ {
+ Texture2D cachedColorMap = readableScaledSpaceMaps.GetValueOrDefault(b);
+ CPUTexture2D mappedColorMap = mappableScaledSpaceMaps.GetValueOrDefault(b);
+
+ // If no textures are loaded, return static
+ if (cachedColorMap == null && mappedColorMap == null)
+ {
+ return palette.lerp(palette.Black, palette.White, UnityEngine.Random.value);
+ }
+
+ // Using doubles because Unity floats apparently default to double and this is faster to just avoid that
+ Color32 c = palette.Grey;
+
+ float fLat = ((float)lat + 90f) / 180f;
+ float fLon = ((float)lon + 270f) / 360f;
+
+ if (fLon < 0) { fLon += 1; }
+ if (fLon > 1) { fLon -= 1; }
+ fLon = 1 - fLon;
+
+ fLat = Mathf.Clamp01(fLat);
+ fLon = Mathf.Clamp01(fLon);
+
+
+ if (mappedColorMap != null)
+ {
+ c = mappedColorMap.GetPixelBilinear(fLon, fLat);
+ }
+ else
+ {
+ c = cachedColorMap.GetPixelBilinear(fLon, fLat);
+ }
+
+ c.a = 255; // Ensure map is fully opaque regardless of source texture alpha
+ return c;
}
public static List EncodeTerrainConfigs
@@ -282,44 +319,63 @@ public static void setMasterTerrainNodes(List terrainConfigs)
}
}
- public static void checkLoadedTerrainNodes()
+ public static void generateTerrainConfig(CelestialBody b)
{
- for (int i = 0; i < FlightGlobals.Bodies.Count; i++)
+ if (b.pqsController == null)
{
- CelestialBody b = FlightGlobals.Bodies[i];
+ SCANUtil.SCANlog($"[{b.name}] PQS Controller not loaded - no terrain data generated.");
+ return;
+ }
- if (b == null)
- {
- continue;
- }
+ float? clamp = null;
+ if (b.ocean)
+ {
+ clamp = 0;
+ }
- float? clamp = null;
- if (b.ocean)
+ float newMin;
+ float newMax;
+
+ try
+ {
+ newMin = ((float)(b.pqsController.radiusMin - b.pqsController.radius)).Mathf_Round(-1);
+ newMax = ((float)(b.pqsController.radiusMax - b.pqsController.radius)).Mathf_Round(-1);
+ if (newMin == newMax)
{
- clamp = 0;
+ throw new Exception("Gas Giant / Flat Body"); // Clamp altimetry if body is perfectly smooth / gas giant
}
+ }
+ catch (Exception e)
+ {
+ SCANUtil.SCANlog($"[{b.name}] Error in calculating Max Height; using default value\n{e}");
+ newMin = SCANconfigLoader.SCANNode.DefaultMinHeightRange;
+ newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
+ }
- float newMin;
- float newMax;
+ addToTerrainConfigData(b.bodyName, new SCANterrainConfig(newMin, newMax, clamp, SCANUtil.PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b));
+ }
- try
- {
- newMin = ((float)(b.pqsController.radiusMin - b.pqsController.radius)).Mathf_Round(-1);
- newMax = ((float)(b.pqsController.radiusMax - b.pqsController.radius)).Mathf_Round(-1);
- if (newMin == newMax)
- {
- throw new Exception("Gas Giant / Flat Body"); // Clamp altimetry if body is perfectly smooth / gas giant
- }
- }
- catch (Exception e)
+ //public void regenerateTerrainConfig(CelestialBody b)
+ //{
+ // if (b == null)
+ // {
+ // return;
+ // }
+ // addToTerrainConfigData(b.bodyName, generateTerrainConfig(b));
+ //}
+
+ public static void checkLoadedTerrainNodes()
+ {
+ for (int i = 0; i < FlightGlobals.Bodies.Count; i++)
+ {
+ CelestialBody b = FlightGlobals.Bodies[i];
+
+ if (b == null)
{
- SCANUtil.SCANlog($"[{b.name}] Error in calculating Max Height; using default value\n{e}");
- newMin = SCANconfigLoader.SCANNode.DefaultMinHeightRange;
- newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
+ continue;
}
- SCANUtil.SCANlog($"[{b.bodyName}] Terrain Config Generation: Heights between {newMin:F0}m and {newMax:F0}m. Clamp is {clamp}.");
- addToTerrainConfigData(b.bodyName, new SCANterrainConfig(newMin, newMax, clamp, SCANUtil.PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b));
+ generateTerrainConfig(b);
}
}
@@ -402,7 +458,7 @@ public static void setMasterResourceNodes(List resourceConfi
{
continue;
}
- //SCANUtil.SCANlog("Loading SCANsat resource config settings: {0}", r.Name);
+
if (!masterResourceNodes.Contains(r.Name))
{
masterResourceNodes.Add(r.Name, r);
@@ -1584,7 +1640,6 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
material = scaledMesh.sharedMaterial; // TODO: what if there are multiple materials? do we need to check all of them?
string shaderName = material.shader.name;
- SCANUtil.SCANdebugLog($"[{b.name}] Material loaded {shaderName}");
if (shaderName == "Terrain/Gas Giant")
{
@@ -1600,7 +1655,7 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
string contains_map = material.HasProperty("_ColorMap") ? "_ColorMap" : null;
string contains_cube = material.HasProperty("_ColorCube") ? "_ColorCube" : null;
SCANUtil.SCANdebugLog($"[{b.name}] Material uses Parallax and contains: {contains_main}, {contains_map}, {contains_cube}.");
- colorMapTextureName = contains_cube != null ? contains_cube : "_ColorMap";
+ colorMapTextureName = "_ColorMap";
return;
}
else if (material.HasProperty("_MainTex"))
@@ -1616,7 +1671,7 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
/// Caches visual textures (colour map)
void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextureName, bool useMaterialForColorMap)
{
- if (readableScaledSpaceMaps.GetValueOrDefault(b) != null)
+ if (readableScaledSpaceMaps.GetValueOrDefault(b) != null || mappableScaledSpaceMaps.GetValueOrDefault(b) != null)
{
return; // Already cached
}
@@ -1632,7 +1687,6 @@ void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextu
if (sourceColorTexture != null)
{
colorMap = sourceColorTexture.isReadable ? sourceColorTexture : readableTexture(sourceColorTexture, useMaterialForColorMap ? material : null);
- SCANUtil.SCANdebugLog($"[{b.name}] {colorMapTextureName} Texture Size is {colorMap.width} x {colorMap.height}.");
readableScaledSpaceMaps.Add(b, colorMap);
}
else
@@ -1644,7 +1698,7 @@ void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextu
}
catch (Exception e)
{
- Log.Error($"Error caching visual texture with luminance for body {b.name}: {e}");
+ Log.Error($"Error caching visual texture from materials for body {b.name}: {e}");
}
}
@@ -1660,6 +1714,45 @@ internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
return;
}
+ switch (s)
+ {
+ case mapSource.BigMap:
+ bigMapBodyScaledSpace = b;
+ break;
+ case mapSource.ZoomMap:
+ zoomMapBodyScaledSpace = b;
+ break;
+ }
+
+ if (mappableScaledSpaceMaps.GetValueOrDefault(b) != null)
+ {
+ return; // Already cached
+ }
+
+ ConfigNode[] visualOverrides = GameDatabase.Instance.GetConfigNodes("SCANSAT_BODY_TEXTURES");
+
+ for (int i = 0; i < visualOverrides.Length; i++)
+ {
+ ConfigNode node = visualOverrides[i];
+ if (node.HasValue("name") && node.HasValue("colorMap"))
+ {
+ string bodyName = node.GetValue("name");
+ if (bodyName == b.name)
+ {
+ string baseFolder = System.IO.Directory.GetParent(KSPUtil.ApplicationRootPath).FullName;
+ string colorMapCPUTextureName = (baseFolder + '/' + node.GetValue("colorMap")).Replace("\\", "/");
+ CPUTextureHandle colorMap = TextureLoader.LoadCPUTexture(colorMapCPUTextureName);
+ if (colorMap.IsError)
+ {
+ Log.Error($"[{b.name}] Visual Data path not loaded: {colorMapCPUTextureName}");
+ return;
+ }
+ mappableScaledSpaceMaps.Add(b, colorMap.GetTexture());
+ return;
+ }
+ }
+ }
+
GetVisualMapTexturesForBody(b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName);
if (material == null)
@@ -1671,16 +1764,6 @@ internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
// Load color map
CacheVisualTexture(b, material, colorMapTextureName, useMaterialForColorMap);
}
-
- switch (s)
- {
- case mapSource.BigMap:
- bigMapBodyScaledSpace = b;
- break;
- case mapSource.ZoomMap:
- zoomMapBodyScaledSpace = b;
- break;
- }
}
internal void UnloadVisualMapTexture(CelestialBody b, mapSource s)
@@ -1723,6 +1806,13 @@ internal void UnloadVisualMapTexture(CelestialBody b, mapSource s)
readableScaledSpaceMaps[b] = null;
readableScaledSpaceMaps.Remove(b);
}
+
+ if (mappableScaledSpaceMaps.ContainsKey(b))
+ {
+ // TODO: Do I need to destroy these textures as well?
+ mappableScaledSpaceMaps[b] = null;
+ mappableScaledSpaceMaps.Remove(b);
+ }
}
private Texture2D readableTexture(Texture tex, Material mat)
@@ -2252,8 +2342,6 @@ private void loadCustomResourceValues(ConfigNode node)
res.MinValue = min;
res.MaxValue = max;
}
- //else
- //SCANUtil.SCANlog("No resources found assigned for Celestial Body: {0}, skipping...", b.bodyName);
}
else
{
diff --git a/SCANsat/SCANsat.csproj b/SCANsat/SCANsat.csproj
index 3ef2d95f6..66ff03d2a 100644
--- a/SCANsat/SCANsat.csproj
+++ b/SCANsat/SCANsat.csproj
@@ -15,6 +15,13 @@
1.12.3
+
+
+ GameData/KSPTextureLoader/Plugins/KSPTextureLoader.dll
+ KSPTextureLoader
+ 1.0.34
+
+
From dc6740fde73995e7b08166a69ebfdf867b3b22e4 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Sun, 31 May 2026 12:33:51 +1200
Subject: [PATCH 05/20] Shove references to the correct terrainConfig
everywhere throughout the plotting code
---
SCANsat/SCAN_Map/SCANmap.cs | 5 +++++
SCANsat/SCAN_UI/UI_Framework/SCANcolorUtil.cs | 4 ++++
SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs | 4 ++++
SCANsat/SCANcontroller.cs | 2 ++
4 files changed, 15 insertions(+)
diff --git a/SCANsat/SCAN_Map/SCANmap.cs b/SCANsat/SCAN_Map/SCANmap.cs
index 9972d602c..d76f66ea2 100644
--- a/SCANsat/SCAN_Map/SCANmap.cs
+++ b/SCANsat/SCAN_Map/SCANmap.cs
@@ -1014,6 +1014,11 @@ internal Texture2D getPartialMap(bool apply = true)
else if (SCANUtil.isCovered(lon, lat, data, SCANtype.Altimetry))
{
projVal = terrainElevation(lon, lat, mapwidth, mapheight, big_heightmap, cache, data, out nowColor);
+ if (data.TerrainConfig == null)
+ {
+ data.TerrainConfig = SCANcontroller.getTerrainNode(data.Body.name);
+ }
+
if (useCustomRange)
{
baseColor = palette.heightToColor(projVal, nowColor, data.TerrainConfig, customMin, customMax, customRange, true);
diff --git a/SCANsat/SCAN_UI/UI_Framework/SCANcolorUtil.cs b/SCANsat/SCAN_UI/UI_Framework/SCANcolorUtil.cs
index 3fc24d36d..73f4e9708 100644
--- a/SCANsat/SCAN_UI/UI_Framework/SCANcolorUtil.cs
+++ b/SCANsat/SCAN_UI/UI_Framework/SCANcolorUtil.cs
@@ -162,6 +162,10 @@ public static Color32 combineColors(Color32[] colors)
public static Color32 heightToColor(float val, bool color, SCANterrainConfig terrain, float min = 0, float max = 0, float range = 0, bool useCustomRange = false)
{
+ if (terrain == null)
+ {
+ return lerp(Black, White, UnityEngine.Random.value);
+ }
Color32[] c = terrain.ColorPal.ColorsArray;
if (terrain.PalRev)
{
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
index 317c30459..cb78fc5ac 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
@@ -956,6 +956,10 @@ private Color32 getMapPixelColor(MainMapDisplayMode display, int scanline, int i
}
else if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.Altimetry))
{
+ if (data.TerrainConfig == null)
+ {
+ data.TerrainConfig = SCANcontroller.getTerrainNode(data.Body.name);
+ }
float val = data.HeightMapValue(data.Body.flightGlobalsIndex, ilon, scanline);
if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.AltimetryHiRes))
{
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index b5624e184..39ad33183 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -833,6 +833,7 @@ public override void OnSave(ConfigNode node)
{
ConfigNode node_body = new ConfigNode("Body");
SCANdata body_scan = body_data[body_name];
+ body_scan.TerrainConfig = getTerrainNode(body_name);
node_body.AddValue("Name", body_name);
node_body.AddValue("Disabled", body_scan.Disabled);
if (SCANmainMenuLoader.MechJebLoaded && SCAN_Settings_Config.Instance.MechJebTarget && SCAN_Settings_Config.Instance.MechJebTargetLoad)
@@ -2236,6 +2237,7 @@ private void SOIChange(GameEvents.HostedFromToAction VC)
if (!body_data.Contains(VC.to.bodyName))
{
body_data.Add(VC.to.bodyName, new SCANdata(VC.to));
+ body_data[VC.to.bodyName].TerrainConfig = getTerrainNode(VC.to.bodyName); // Force SCANdata to have correct terrain
}
}
From 88e5227f42662fdd90770dae435c118cf464cb26 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Sat, 6 Jun 2026 15:05:04 +1200
Subject: [PATCH 06/20] Added reader support for heightMap and normalMap
parameters, and added the baked lighting improvements back in now that I know
they rely on Y axis normal instead of Z.
---
SCANsat/SCANcontroller.cs | 102 ++++++++++++++++++++++++++++++++------
1 file changed, 87 insertions(+), 15 deletions(-)
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index 39ad33183..0d7ebc37c 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -31,6 +31,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
+using System.Text;
using UnityEngine;
using UnityEngine.Events;
using Log = KSPCommunityLib.Logging.Log;
@@ -156,10 +157,14 @@ public static SCANcontroller controller
/* Visual Map Texture Data */
private Dictionary readableScaledSpaceMaps = new Dictionary();
- private Dictionary mappableScaledSpaceMaps = new Dictionary();
private CelestialBody bigMapBodyScaledSpace;
private CelestialBody zoomMapBodyScaledSpace;
+ /* Memory Mapped Data (requires SCANSAT_BODY_TEXTURES config) */
+ private Dictionary memoryMappedHeightMaps = new Dictionary();
+ private Dictionary memoryMappedNormalMaps = new Dictionary();
+ private Dictionary memoryMappedVisualMaps = new Dictionary();
+
private SCAN_UI_MainMap _mainMap;
private SCAN_UI_Instruments _instruments;
private SCAN_UI_BigMap _bigMap;
@@ -243,7 +248,7 @@ public bool isVisualTextureLoaded(CelestialBody b)
}
// Check if a texture is present in either dictionary
- if (readableScaledSpaceMaps.GetValueOrDefault(b) != null || mappableScaledSpaceMaps.GetValueOrDefault(b) != null)
+ if (readableScaledSpaceMaps.GetValueOrDefault(b) != null || memoryMappedVisualMaps.GetValueOrDefault(b) != null)
{
return true;
}
@@ -254,7 +259,7 @@ public bool isVisualTextureLoaded(CelestialBody b)
public Color32 GetVisualPixel(CelestialBody b, double lon, double lat)
{
Texture2D cachedColorMap = readableScaledSpaceMaps.GetValueOrDefault(b);
- CPUTexture2D mappedColorMap = mappableScaledSpaceMaps.GetValueOrDefault(b);
+ CPUTexture2D mappedColorMap = memoryMappedVisualMaps.GetValueOrDefault(b);
// If no textures are loaded, return static
if (cachedColorMap == null && mappedColorMap == null)
@@ -279,13 +284,49 @@ public Color32 GetVisualPixel(CelestialBody b, double lon, double lat)
if (mappedColorMap != null)
{
c = mappedColorMap.GetPixelBilinear(fLon, fLat);
+
+ // Attempt to load Normal map values
+ CPUTexture2D mappedNormalMap = memoryMappedNormalMaps.GetValueOrDefault(b);
+ if (mappedNormalMap != null) {
+ Color32 n = mappedNormalMap.GetPixelBilinear(fLon, fLat);
+ float lumOver = n.b / 255f; // Base game KSP blue channel to store Y axis normal data
+
+ switch(mappedNormalMap.Format)
+ {
+ case TextureFormat.BC5:
+ lumOver = n.g / 255f; // BC5 stores X in red and Y in green (Z not stored)
+ break;
+ case TextureFormat.DXT5:
+ lumOver = n.g / 255f; // DXT5 stores X in alpha and Y in green (Z not stored)
+ break;
+ default:
+ break;
+ }
+
+ HslColor hslBase = palette.ConvertRgbToHsl(c);
+
+ double opacity = 0.8;
+ double lum = hslBase.L;
+
+ if (lum > 0.5d)
+ {
+ lum = (opacity * (1 - (1 - (2 * (lumOver - 0.5))) * (1 - lum))) + (1 - opacity) * lum;
+ }
+ else
+ {
+ lum = (opacity * (2 * lumOver * lum)) + (1 - opacity) * lum;
+ }
+
+ c = palette.ConvertHslToRgb(hslBase.H, hslBase.S, lum);
+ }
+ c.a = 255; // Ensure map is fully opaque regardless of source texture alpha
}
else
{
c = cachedColorMap.GetPixelBilinear(fLon, fLat);
+ c.a = 255; // Ensure map is fully opaque regardless of source texture alpha
}
- c.a = 255; // Ensure map is fully opaque regardless of source texture alpha
return c;
}
@@ -1672,7 +1713,7 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
/// Caches visual textures (colour map)
void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextureName, bool useMaterialForColorMap)
{
- if (readableScaledSpaceMaps.GetValueOrDefault(b) != null || mappableScaledSpaceMaps.GetValueOrDefault(b) != null)
+ if (readableScaledSpaceMaps.GetValueOrDefault(b) != null || memoryMappedVisualMaps.GetValueOrDefault(b) != null)
{
return; // Already cached
}
@@ -1725,7 +1766,7 @@ internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
break;
}
- if (mappableScaledSpaceMaps.GetValueOrDefault(b) != null)
+ if (memoryMappedVisualMaps.GetValueOrDefault(b) != null)
{
return; // Already cached
}
@@ -1741,14 +1782,45 @@ internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
if (bodyName == b.name)
{
string baseFolder = System.IO.Directory.GetParent(KSPUtil.ApplicationRootPath).FullName;
- string colorMapCPUTextureName = (baseFolder + '/' + node.GetValue("colorMap")).Replace("\\", "/");
- CPUTextureHandle colorMap = TextureLoader.LoadCPUTexture(colorMapCPUTextureName);
- if (colorMap.IsError)
+
+ // Attempt to load heightMap
+ if (!memoryMappedHeightMaps.ContainsKey(b))
{
- Log.Error($"[{b.name}] Visual Data path not loaded: {colorMapCPUTextureName}");
- return;
+ string heightMapCPUTextureName = (baseFolder + '/' + node.GetValue("heightMap")).Replace("\\", "/");
+ CPUTextureHandle heightMap = TextureLoader.LoadCPUTexture(heightMapCPUTextureName);
+ if (heightMap.IsError)
+ {
+ Log.Error($"[{b.name}] Height Map Path not defined: {heightMapCPUTextureName}");
+ return;
+ }
+ memoryMappedHeightMaps.Add(b, heightMap.GetTexture());
+ }
+
+ // Attempt to load normalMap
+ if (!memoryMappedNormalMaps.ContainsKey(b))
+ {
+ string normalMapCPUTextureName = (baseFolder + '/' + node.GetValue("normalMap")).Replace("\\", "/");
+ CPUTextureHandle normalMap = TextureLoader.LoadCPUTexture(normalMapCPUTextureName);
+ if (normalMap.IsError)
+ {
+ Log.Error($"[{b.name}] Normal Map Path not defined: {normalMapCPUTextureName}");
+ return;
+ }
+ memoryMappedNormalMaps.Add(b, normalMap.GetTexture());
+ }
+
+ // Attempt to load colorMap
+ if (!memoryMappedVisualMaps.ContainsKey(b))
+ {
+ string colorMapCPUTextureName = (baseFolder + '/' + node.GetValue("colorMap")).Replace("\\", "/");
+ CPUTextureHandle colorMap = TextureLoader.LoadCPUTexture(colorMapCPUTextureName);
+ if (colorMap.IsError)
+ {
+ Log.Error($"[{b.name}] Visual Data path not loaded: {colorMapCPUTextureName}");
+ return;
+ }
+ memoryMappedVisualMaps.Add(b, colorMap.GetTexture());
}
- mappableScaledSpaceMaps.Add(b, colorMap.GetTexture());
return;
}
}
@@ -1808,11 +1880,11 @@ internal void UnloadVisualMapTexture(CelestialBody b, mapSource s)
readableScaledSpaceMaps.Remove(b);
}
- if (mappableScaledSpaceMaps.ContainsKey(b))
+ if (memoryMappedVisualMaps.ContainsKey(b))
{
// TODO: Do I need to destroy these textures as well?
- mappableScaledSpaceMaps[b] = null;
- mappableScaledSpaceMaps.Remove(b);
+ memoryMappedVisualMaps[b] = null;
+ memoryMappedVisualMaps.Remove(b);
}
}
From 9af3a8d248f335666d66f42aefba80b9a49561bb Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Thu, 25 Jun 2026 22:40:09 +1200
Subject: [PATCH 07/20] Fix biome legend entries.
SCANUtil palette generation now copies all parameters from existing definitions before overwriting mapColor, preserving display name. All legend entry calls now made with palette references to allow recolouring. Remove duplicate code from getLegend() generation - better optimizing of block splitting required.
---
SCANsat/SCAN_Data/SCANdata.cs | 38 +++++++++++++++++++
SCANsat/SCAN_Map/SCANmap.cs | 4 --
SCANsat/SCAN_Map/SCANmapLegend.cs | 54 +++++----------------------
SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs | 2 +-
SCANsat/SCAN_Unity/SCAN_UI_Color.cs | 4 +-
SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs | 4 --
SCANsat/SCANutil.cs | 14 +++----
7 files changed, 56 insertions(+), 64 deletions(-)
diff --git a/SCANsat/SCAN_Data/SCANdata.cs b/SCANsat/SCAN_Data/SCANdata.cs
index 3fa53cd1e..e3471e78c 100644
--- a/SCANsat/SCAN_Data/SCANdata.cs
+++ b/SCANsat/SCAN_Data/SCANdata.cs
@@ -48,6 +48,7 @@ internal SCANdata(CelestialBody b)
body = b;
coverage = new Int16[360, 180];
+ terrainConfig = generateTerrainConfig(body); // Generate correct config on initialization
if (heightMaps.ContainsKey(body.flightGlobalsIndex))
{
@@ -885,6 +886,43 @@ internal void generateHeightMap(ref int step, ref int xStart, int width)
xStart += width;
}
+
+ public static SCANterrainConfig generateTerrainConfig(CelestialBody b)
+ {
+ if (b.pqsController == null)
+ {
+ SCANUtil.SCANlog($"[{b.name}] PQS Controller not loaded - no terrain data generated.");
+ return null;
+ }
+
+ float? clamp = null;
+ if (b.ocean)
+ {
+ clamp = 0;
+ }
+
+ float newMin;
+ float newMax;
+
+ try
+ {
+ newMin = ((float)(b.pqsController.radiusMin - b.pqsController.radius)).Mathf_Round(-1);
+ newMax = ((float)(b.pqsController.radiusMax - b.pqsController.radius)).Mathf_Round(-1);
+ if (newMin == newMax)
+ {
+ throw new Exception("Gas Giant / Flat Body"); // Clamp altimetry if body is perfectly smooth / gas giant
+ }
+ }
+ catch (Exception e)
+ {
+ SCANUtil.SCANlog($"[{b.name}] Error in calculating Max Height; using default value\n{e}");
+ newMin = SCANconfigLoader.SCANNode.DefaultMinHeightRange;
+ newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
+ }
+
+ return new SCANterrainConfig(newMin, newMax, clamp, SCANUtil.PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b);
+ }
+
#endregion
#region Map Utilities
diff --git a/SCANsat/SCAN_Map/SCANmap.cs b/SCANsat/SCAN_Map/SCANmap.cs
index d76f66ea2..35a7b7f32 100644
--- a/SCANsat/SCAN_Map/SCANmap.cs
+++ b/SCANsat/SCAN_Map/SCANmap.cs
@@ -1014,10 +1014,6 @@ internal Texture2D getPartialMap(bool apply = true)
else if (SCANUtil.isCovered(lon, lat, data, SCANtype.Altimetry))
{
projVal = terrainElevation(lon, lat, mapwidth, mapheight, big_heightmap, cache, data, out nowColor);
- if (data.TerrainConfig == null)
- {
- data.TerrainConfig = SCANcontroller.getTerrainNode(data.Body.name);
- }
if (useCustomRange)
{
diff --git a/SCANsat/SCAN_Map/SCANmapLegend.cs b/SCANsat/SCAN_Map/SCANmapLegend.cs
index c2e5a156a..5554865e9 100644
--- a/SCANsat/SCAN_Map/SCANmapLegend.cs
+++ b/SCANsat/SCAN_Map/SCANmapLegend.cs
@@ -17,6 +17,7 @@
using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
using UnityEngine;
+using System.Collections.Generic;
namespace SCANsat.SCAN_Map
{
@@ -100,67 +101,30 @@ public Texture2D getLegend(SCANdata data, bool color, bool stock, CBAttributeMap
Color32[] pix = new Color32[256];
int count = biomes.Length;
-
int blockSize = (int)Math.Truncate(256 / (count * 1d));
- int total = 0;
-
- for (int i = 0; i < count; i++)
+ for (int biome_idx = 0; biome_idx < count; biome_idx++)
{
- for (int j = 0; j < blockSize; j++)
- {
- if (total >= 256)
- {
- break;
- }
-
- int current = i * blockSize + j;
+ int start = biome_idx * blockSize;
+ int end = biome_idx == count - 1 ? 256: start + blockSize; // Ensure the last block fills to the end of the texture
+ for (int i = start; i < end; i++)
+ {
if (stock && color)
{
- pix[current] = SCANUtil.getBiomeDisplayColor(data.Body, i);
+ pix[i] = biomes[biome_idx].mapColor;
}
else if (color)
{
- pix[current] = palette.lerp(SCANcontroller.controller.lowBiomeColor32, SCANcontroller.controller.highBiomeColor32, (float)((i * 1f) / (count * 1f)));
+ pix[i] = palette.lerp(SCANcontroller.controller.lowBiomeColor32, SCANcontroller.controller.highBiomeColor32, (float)((biome_idx * 1f) / (count * 1f)));
}
else
{
- pix[current] = palette.lerp(palette.Black, palette.White, (float)(i * 1f) / (count * 1f));
+ pix[i] = palette.lerp(palette.Black, palette.White, (float)(biome_idx * 1f) / (count * 1f));
}
-
- total++;
}
}
- int remaining = 256 - count * blockSize;
- int backCount = remaining;
-
- for (int i = 0; i < remaining; i++)
- {
- if (total > 256 || backCount <= 0)
- {
- break;
- }
-
- if (SCAN_Settings_Config.Instance.BigMapStockBiomes && color)
- {
- pix[256 - backCount] = SCANUtil.getBiomeDisplayColor(data.Body, count - 1);
- }
- else if (color)
- {
- pix[256 - backCount] = palette.lerp(SCANcontroller.controller.lowBiomeColor32, SCANcontroller.controller.highBiomeColor32, (float)(((count - 1) * 1f) / (count * 1f)));
- }
- else
- {
- pix[256 - backCount] = palette.lerp(palette.Black, palette.White, (float)(((count - 1) * 1f) / (count * 1f)));
- }
-
- backCount--;
-
- total++;
- }
-
legend.SetPixels32(pix);
legend.Apply();
return legend;
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs
index df9fe2203..e211c36dd 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs
@@ -1784,7 +1784,7 @@ public Texture2D LegendImage
case mapType.Biome:
if (body != null && body.BiomeMap != null && body.BiomeMap.Attributes != null)
{
- return bigmap.MapLegend.getLegend(data, SCANcontroller.controller.bigMapColor, SCAN_Settings_Config.Instance.BigMapStockBiomes, body.BiomeMap.Attributes);
+ return bigmap.MapLegend.getLegend(data, SCANcontroller.controller.bigMapColor, SCAN_Settings_Config.Instance.BigMapStockBiomes, SCANUtil.GetOrBuildPalette(body));
}
else
{
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_Color.cs b/SCANsat/SCAN_Unity/SCAN_UI_Color.cs
index e4b3e275a..877f849c1 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_Color.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_Color.cs
@@ -122,7 +122,7 @@ public void Refresh()
_resourceTransparency = currentResource.Transparency;
}
- currentTerrain = SCANcontroller.getTerrainNode(_terrainPlanet);
+ currentTerrain = SCANUtil.getData(_terrainPlanet).TerrainConfig;
if (currentTerrain != null)
{
@@ -211,7 +211,7 @@ public string TerrainPlanet
_terrainPlanet = body;
- currentTerrain = SCANcontroller.getTerrainNode(body);
+ currentTerrain = SCANUtil.getData(body).TerrainConfig;
if (currentTerrain != null)
{
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
index cb78fc5ac..317c30459 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
@@ -956,10 +956,6 @@ private Color32 getMapPixelColor(MainMapDisplayMode display, int scanline, int i
}
else if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.Altimetry))
{
- if (data.TerrainConfig == null)
- {
- data.TerrainConfig = SCANcontroller.getTerrainNode(data.Body.name);
- }
float val = data.HeightMapValue(data.Body.flightGlobalsIndex, ilon, scanline);
if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.AltimetryHiRes))
{
diff --git a/SCANsat/SCANutil.cs b/SCANsat/SCANutil.cs
index 2a524e200..1a36105b7 100644
--- a/SCANsat/SCANutil.cs
+++ b/SCANsat/SCANutil.cs
@@ -850,10 +850,12 @@ private static bool IsGrayscaleEncoded(CBAttributeMapSO.MapAttribute[] attrs)
return true;
}
- private static CBAttributeMapSO.MapAttribute[] GetOrBuildPalette(CelestialBody body)
+ public static CBAttributeMapSO.MapAttribute[] GetOrBuildPalette(CelestialBody body)
{
if (_scanBiomePaletteCache.TryGetValue(body.name, out var cached))
+ {
return cached;
+ }
var src = body.BiomeMap.Attributes;
if (!IsGrayscaleEncoded(src))
@@ -868,13 +870,9 @@ private static CBAttributeMapSO.MapAttribute[] GetOrBuildPalette(CelestialBody b
var replacements = new CBAttributeMapSO.MapAttribute[src.Length];
for (int i = 0; i < src.Length; i++)
{
- if (src[i] == null) continue;
- var clone = new CBAttributeMapSO.MapAttribute
- {
- name = src[i].name,
- value = src[i].value,
- mapColor = Color.HSVToRGB((i * 0.61803398875f) % 1f, 1f, 1f)
- };
+ if (src[i] == null) { continue; }
+ var clone = new CBAttributeMapSO.MapAttribute(src[i]); // Copy all attributes
+ clone.mapColor = Color.HSVToRGB((i * 0.61803398875f) % 1f, 1f, 1f);
replacements[i] = clone;
}
_scanBiomePaletteCache[body.name] = replacements;
From fe4a2c43fe119902ba205c53ff393acd613da52c Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Thu, 25 Jun 2026 22:48:38 +1200
Subject: [PATCH 08/20] Make biome legend entries space more evenly.
---
SCANsat/SCAN_Map/SCANmapLegend.cs | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/SCANsat/SCAN_Map/SCANmapLegend.cs b/SCANsat/SCAN_Map/SCANmapLegend.cs
index 5554865e9..9aa00c67e 100644
--- a/SCANsat/SCAN_Map/SCANmapLegend.cs
+++ b/SCANsat/SCAN_Map/SCANmapLegend.cs
@@ -101,12 +101,10 @@ public Texture2D getLegend(SCANdata data, bool color, bool stock, CBAttributeMap
Color32[] pix = new Color32[256];
int count = biomes.Length;
- int blockSize = (int)Math.Truncate(256 / (count * 1d));
-
for (int biome_idx = 0; biome_idx < count; biome_idx++)
{
- int start = biome_idx * blockSize;
- int end = biome_idx == count - 1 ? 256: start + blockSize; // Ensure the last block fills to the end of the texture
+ int start = (int) Math.Round(biome_idx * 256 / (count * 1d));
+ int end = (int) Math.Round((biome_idx + 1) * 256 / (count * 1d));
for (int i = start; i < end; i++)
{
From 506ef0fe61b06187855ee7ef6d101e9bb1eba789 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Sat, 27 Jun 2026 10:56:54 +1200
Subject: [PATCH 09/20] Configs for altimetry broken, but colour methods fixed.
Don't use this yet though in stock - will work on this.
---
GameData/SCANsat/SCANsat.version | 7 +-
SCANsat/SCAN_Color_Config.cs | 4 +-
SCANsat/SCAN_Map/SCANmap.cs | 6 +-
SCANsat/SCANcontroller.cs | 129 +++++++++----------------------
SCANsat/SCANutil.cs | 15 ++++
5 files changed, 61 insertions(+), 100 deletions(-)
diff --git a/GameData/SCANsat/SCANsat.version b/GameData/SCANsat/SCANsat.version
index af2031d83..2b4319d5c 100644
--- a/GameData/SCANsat/SCANsat.version
+++ b/GameData/SCANsat/SCANsat.version
@@ -1,6 +1,7 @@
{
"NAME": "SCANsat",
- "URL": "https://github.com/KSPModStewards/SCANsat/releases/latest/download/SCANsat.version",
- "DOWNLOAD": "https://github.com/KSPModStewards/SCANsat/releases"
-
+ "VERSION": "21.1.0.0",
+ "KSP_VERSION": "1.12",
+ "KSP_VERSION_MIN": "1.12.3",
+ "KSP_VERSION_MAX": "1.12"
}
diff --git a/SCANsat/SCAN_Color_Config.cs b/SCANsat/SCAN_Color_Config.cs
index 8c630e783..c32e766a4 100644
--- a/SCANsat/SCAN_Color_Config.cs
+++ b/SCANsat/SCAN_Color_Config.cs
@@ -53,7 +53,7 @@ internal SCAN_Color_Config(string filepath, string node)
public override void OnDecodeFromConfigNode()
{
- SCANcontroller.setMasterTerrainNodes(SCANsat_Altimetry);
+ SCANcontroller.DecodeTerrainConfigs(SCANsat_Altimetry);
SCANcontroller.setMasterResourceNodes(SCANsat_Resources);
}
@@ -61,7 +61,7 @@ public override void OnEncodeToConfigNode()
{
SCANUtil.SCANlog("Saving SCANsat configuration file...");
SCANUtil.SCANlog("SCANcolors.cfg saved to ---> {0}", FilePath);
- SCANsat_Altimetry = SCANcontroller.EncodeTerrainConfigs;
+ SCANsat_Altimetry = SCANcontroller.EncodeTerrainConfigs();
SCANsat_Resources = SCANcontroller.EncodeResourceConfigs;
}
diff --git a/SCANsat/SCAN_Map/SCANmap.cs b/SCANsat/SCAN_Map/SCANmap.cs
index 35a7b7f32..d4f488cdd 100644
--- a/SCANsat/SCAN_Map/SCANmap.cs
+++ b/SCANsat/SCAN_Map/SCANmap.cs
@@ -1174,7 +1174,7 @@ internal Texture2D getPartialMap(bool apply = true)
{
if (!SCANcontroller.controller.isVisualTextureLoaded(body))
{
- baseColor = unscanned;
+ baseColor = palette.lerp(palette.Black, palette.White, UnityEngine.Random.value);
break;
}
@@ -1197,6 +1197,10 @@ internal Texture2D getPartialMap(bool apply = true)
baseColor = palette.ConvertToGrayscale(baseColor);
}
}
+ else
+ {
+ baseColor = unscanned;
+ }
break;
}
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index 0d7ebc37c..4abd98698 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -132,16 +132,13 @@ public static SCANcontroller controller
/* Available resources for overlays; loaded from SCANsat configs; only loaded once */
private static DictionaryValueList masterResourceNodes = new DictionaryValueList();
- /* Terrain height and color option containers loaded from SCANsat configs; only needs to be loaded once */
- private static Dictionary masterTerrainNodes = new Dictionary();
-
/* List of resources currently loaded from resource addons */
private static List loadedResources = new List();
/* Primary SCANsat vessel dictionary; loaded every time */
public DictionaryValueList knownVessels = new DictionaryValueList();
- /* Primary SCANdata dictionary; loaded every time*/
+ /* Primary SCANdata dictionary; loaded every time */
private DictionaryValueList body_data = new DictionaryValueList();
/* MechJeb Landing Target Integration */
@@ -330,80 +327,30 @@ public Color32 GetVisualPixel(CelestialBody b, double lon, double lat)
return c;
}
- public static List EncodeTerrainConfigs
- {
- get
- {
- try
- {
- return masterTerrainNodes.Values.ToList();
- }
- catch (Exception e)
- {
- SCANUtil.SCANlog("Error while saving SCANsat altimetry config data: {0}", e);
- }
-
- return new List();
- }
- }
- public static void setMasterTerrainNodes(List terrainConfigs)
+ public static List EncodeTerrainConfigs()
{
- masterTerrainNodes.Clear();
- try
- {
- masterTerrainNodes = terrainConfigs.ToDictionary(a => a.Name, a => a);
- }
- catch (Exception e)
- {
- SCANUtil.SCANlog("Error while loading SCANsat terrain config settings: {0}", e);
- }
- }
-
- public static void generateTerrainConfig(CelestialBody b)
- {
- if (b.pqsController == null)
+ List configs = new List();
+
+ for (int i = 0; i < FlightGlobals.Bodies.Count; i++)
{
- SCANUtil.SCANlog($"[{b.name}] PQS Controller not loaded - no terrain data generated.");
- return;
- }
+ CelestialBody b = FlightGlobals.Bodies[i];
- float? clamp = null;
- if (b.ocean)
- {
- clamp = 0;
- }
+ if (b == null)
+ {
+ continue;
+ }
- float newMin;
- float newMax;
+ SCANdata data = SCANUtil.getData(b);
- try
- {
- newMin = ((float)(b.pqsController.radiusMin - b.pqsController.radius)).Mathf_Round(-1);
- newMax = ((float)(b.pqsController.radiusMax - b.pqsController.radius)).Mathf_Round(-1);
- if (newMin == newMax)
+ if (data != null)
{
- throw new Exception("Gas Giant / Flat Body"); // Clamp altimetry if body is perfectly smooth / gas giant
+ configs.Add(data.TerrainConfig);
}
}
- catch (Exception e)
- {
- SCANUtil.SCANlog($"[{b.name}] Error in calculating Max Height; using default value\n{e}");
- newMin = SCANconfigLoader.SCANNode.DefaultMinHeightRange;
- newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
- }
-
- addToTerrainConfigData(b.bodyName, new SCANterrainConfig(newMin, newMax, clamp, SCANUtil.PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b));
- }
+ return configs;
+ }
- //public void regenerateTerrainConfig(CelestialBody b)
- //{
- // if (b == null)
- // {
- // return;
- // }
- // addToTerrainConfigData(b.bodyName, generateTerrainConfig(b));
- //}
public static void checkLoadedTerrainNodes()
{
@@ -416,27 +363,17 @@ public static void checkLoadedTerrainNodes()
continue;
}
- generateTerrainConfig(b);
- }
- }
-
- public static SCANterrainConfig getTerrainNode(string name)
- {
- if (masterTerrainNodes.ContainsKey(name))
- {
- return masterTerrainNodes[name];
- }
- else
- {
- SCANUtil.SCANlog("SCANsat terrain config [{0}] cannot be found in master terrain storage list", name);
+ SCANdata data = SCANUtil.getData(b.bodyName);
+ if (data == null)
+ {
+ SCANUtil.addToBodyData(b, new SCANdata(b)); // Generates terrain config on init
+ }
}
-
- return null;
}
public static void updateTerrainConfig(SCANterrainConfig t)
{
- SCANterrainConfig update = getTerrainNode(t.Name);
+ SCANterrainConfig update = SCANUtil.getData(t.Name).TerrainConfig;
if (update != null)
{
update.MinTerrain = t.MinTerrain;
@@ -449,16 +386,23 @@ public static void updateTerrainConfig(SCANterrainConfig t)
}
}
- public static void addToTerrainConfigData(string name, SCANterrainConfig data)
+ public static void DecodeTerrainConfigs(List configs)
{
- if (masterTerrainNodes.ContainsKey(name))
+ foreach (SCANterrainConfig config in configs)
{
- Log.Warning($"[{name}] Terrain Config already stored in SCANterrain Data Dictionary");
- updateTerrainConfig(data);
- return;
- }
+ if (config == null)
+ {
+ continue;
+ }
- masterTerrainNodes.Add(name, data);
+ SCANdata data = SCANUtil.getData(config.Name);
+ if (data == null)
+ {
+ CelestialBody b = FlightGlobals.Bodies.FirstOrDefault(x => x.bodyName == config.Name);
+ SCANUtil.addToBodyData(b, new SCANdata(b)); // Add SCANdata if not present
+ }
+ updateTerrainConfig(config);
+ }
}
public static int MasterResourceCount
@@ -813,7 +757,6 @@ public override void OnLoad(ConfigNode node)
}
data.Disabled = node_body.parse("Disabled", false);
- data.TerrainConfig = getTerrainNode(body.bodyName);
}
catch (Exception e)
{
@@ -874,7 +817,6 @@ public override void OnSave(ConfigNode node)
{
ConfigNode node_body = new ConfigNode("Body");
SCANdata body_scan = body_data[body_name];
- body_scan.TerrainConfig = getTerrainNode(body_name);
node_body.AddValue("Name", body_name);
node_body.AddValue("Disabled", body_scan.Disabled);
if (SCANmainMenuLoader.MechJebLoaded && SCAN_Settings_Config.Instance.MechJebTarget && SCAN_Settings_Config.Instance.MechJebTargetLoad)
@@ -2309,7 +2251,6 @@ private void SOIChange(GameEvents.HostedFromToAction VC)
if (!body_data.Contains(VC.to.bodyName))
{
body_data.Add(VC.to.bodyName, new SCANdata(VC.to));
- body_data[VC.to.bodyName].TerrainConfig = getTerrainNode(VC.to.bodyName); // Force SCANdata to have correct terrain
}
}
diff --git a/SCANsat/SCANutil.cs b/SCANsat/SCANutil.cs
index 1a36105b7..ccd43f30a 100644
--- a/SCANsat/SCANutil.cs
+++ b/SCANsat/SCANutil.cs
@@ -266,6 +266,21 @@ public static SCANdata getData(string BodyName)
return SCANcontroller.controller.getData(BodyName);
}
+ ///
+ /// For a given Celestial Body name this sets the SCANdata instance with the provided data.
+ ///
+ /// Celestial body (do not use displayName string)
+ /// SCANdata instance for the given Celestial Body; null if none exists
+ public static void addToBodyData(CelestialBody b, SCANdata data)
+ {
+ if (SCANcontroller.controller == null)
+ {
+ return;
+ }
+
+ SCANcontroller.controller.addToBodyData(b, new SCANdata(data));
+ }
+
///
/// Do SCANsat maps automatically update with the stock, instant-scan orbital surveys?
///
From bf3cf1b9e6d00aea8de6ec5b9675a906d283d09b Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Sat, 27 Jun 2026 20:03:35 +1200
Subject: [PATCH 10/20] Changed TerrainConfig storage.
Removed all instances of data.TerrainConfig and replaced with SCANUtil getTerrainConfig methods for CelestialBody, SCANdata and string.
Change legend numbering to round to the nearest 10m instead of 100m (pesky little asteroids). Want to add dynamic rounding here so we get x significant figures instead.
---
SCANsat/SCAN_Color_Config.cs | 4 +-
SCANsat/SCAN_Data/SCANdata.cs | 44 --------
SCANsat/SCAN_Map/SCANmap.cs | 6 +-
SCANsat/SCAN_Map/SCANmapExporter.cs | 2 +-
SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs | 8 +-
SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs | 10 +-
SCANsat/SCAN_Unity/SCAN_UI_Color.cs | 4 +-
SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs | 4 +-
SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs | 8 +-
SCANsat/SCANcontroller.cs | 115 +++++++++++----------
SCANsat/SCANutil.cs | 75 ++++++++++++--
11 files changed, 154 insertions(+), 126 deletions(-)
diff --git a/SCANsat/SCAN_Color_Config.cs b/SCANsat/SCAN_Color_Config.cs
index c32e766a4..8c630e783 100644
--- a/SCANsat/SCAN_Color_Config.cs
+++ b/SCANsat/SCAN_Color_Config.cs
@@ -53,7 +53,7 @@ internal SCAN_Color_Config(string filepath, string node)
public override void OnDecodeFromConfigNode()
{
- SCANcontroller.DecodeTerrainConfigs(SCANsat_Altimetry);
+ SCANcontroller.setMasterTerrainNodes(SCANsat_Altimetry);
SCANcontroller.setMasterResourceNodes(SCANsat_Resources);
}
@@ -61,7 +61,7 @@ public override void OnEncodeToConfigNode()
{
SCANUtil.SCANlog("Saving SCANsat configuration file...");
SCANUtil.SCANlog("SCANcolors.cfg saved to ---> {0}", FilePath);
- SCANsat_Altimetry = SCANcontroller.EncodeTerrainConfigs();
+ SCANsat_Altimetry = SCANcontroller.EncodeTerrainConfigs;
SCANsat_Resources = SCANcontroller.EncodeResourceConfigs;
}
diff --git a/SCANsat/SCAN_Data/SCANdata.cs b/SCANsat/SCAN_Data/SCANdata.cs
index e3471e78c..c0d8ee1db 100644
--- a/SCANsat/SCAN_Data/SCANdata.cs
+++ b/SCANsat/SCAN_Data/SCANdata.cs
@@ -34,7 +34,6 @@ public class SCANdata
/* MAP: state */
internal Int16[,] coverage;
private CelestialBody body;
- private SCANterrainConfig terrainConfig;
private bool mapBuilding, overlayBuilding, controllerBuilding, built;
private float[,] tempHeightMap;
@@ -48,7 +47,6 @@ internal SCANdata(CelestialBody b)
body = b;
coverage = new Int16[360, 180];
- terrainConfig = generateTerrainConfig(body); // Generate correct config on initialization
if (heightMaps.ContainsKey(body.flightGlobalsIndex))
{
@@ -59,7 +57,6 @@ internal SCANdata(CelestialBody b)
public SCANdata(SCANdata copy)
{
coverage = copy.coverage;
- terrainConfig = new SCANterrainConfig(copy.terrainConfig);
if (!heightMaps.ContainsKey(copy.body.flightGlobalsIndex))
{
@@ -107,12 +104,6 @@ public CelestialBody Body
get { return body; }
}
- public SCANterrainConfig TerrainConfig
- {
- get { return terrainConfig; }
- internal set { terrainConfig = value; }
- }
-
public bool Disabled
{
get { return disabled; }
@@ -887,41 +878,6 @@ internal void generateHeightMap(ref int step, ref int xStart, int width)
xStart += width;
}
- public static SCANterrainConfig generateTerrainConfig(CelestialBody b)
- {
- if (b.pqsController == null)
- {
- SCANUtil.SCANlog($"[{b.name}] PQS Controller not loaded - no terrain data generated.");
- return null;
- }
-
- float? clamp = null;
- if (b.ocean)
- {
- clamp = 0;
- }
-
- float newMin;
- float newMax;
-
- try
- {
- newMin = ((float)(b.pqsController.radiusMin - b.pqsController.radius)).Mathf_Round(-1);
- newMax = ((float)(b.pqsController.radiusMax - b.pqsController.radius)).Mathf_Round(-1);
- if (newMin == newMax)
- {
- throw new Exception("Gas Giant / Flat Body"); // Clamp altimetry if body is perfectly smooth / gas giant
- }
- }
- catch (Exception e)
- {
- SCANUtil.SCANlog($"[{b.name}] Error in calculating Max Height; using default value\n{e}");
- newMin = SCANconfigLoader.SCANNode.DefaultMinHeightRange;
- newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
- }
-
- return new SCANterrainConfig(newMin, newMax, clamp, SCANUtil.PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b);
- }
#endregion
diff --git a/SCANsat/SCAN_Map/SCANmap.cs b/SCANsat/SCAN_Map/SCANmap.cs
index d4f488cdd..62179e662 100644
--- a/SCANsat/SCAN_Map/SCANmap.cs
+++ b/SCANsat/SCAN_Map/SCANmap.cs
@@ -1017,11 +1017,11 @@ internal Texture2D getPartialMap(bool apply = true)
if (useCustomRange)
{
- baseColor = palette.heightToColor(projVal, nowColor, data.TerrainConfig, customMin, customMax, customRange, true);
+ baseColor = palette.heightToColor(projVal, nowColor, SCANUtil.getTerrainConfig(data), customMin, customMax, customRange, true);
}
else
{
- baseColor = palette.heightToColor(projVal, nowColor, data.TerrainConfig);
+ baseColor = palette.heightToColor(projVal, nowColor, SCANUtil.getTerrainConfig(data));
}
}
else
@@ -1120,7 +1120,7 @@ internal Texture2D getPartialMap(bool apply = true)
}
else
{
- elevation = palette.lerp(palette.Black, palette.White, Mathf.Clamp(projVal + (-1f * data.TerrainConfig.MinTerrain), 0, data.TerrainConfig.TerrainRange) / data.TerrainConfig.TerrainRange);
+ elevation = palette.lerp(palette.Black, palette.White, Mathf.Clamp(projVal + (-1f * SCANUtil.getTerrainConfig(data).MinTerrain), 0, SCANUtil.getTerrainConfig(data).TerrainRange) / SCANUtil.getTerrainConfig(data).TerrainRange);
}
}
}
diff --git a/SCANsat/SCAN_Map/SCANmapExporter.cs b/SCANsat/SCAN_Map/SCANmapExporter.cs
index e2acc60fa..2cf98fdb3 100644
--- a/SCANsat/SCAN_Map/SCANmapExporter.cs
+++ b/SCANsat/SCAN_Map/SCANmapExporter.cs
@@ -71,7 +71,7 @@ public void exportPNG(SCANmap map, SCANdata data)
ScreenMessages.PostScreenMessage("SCANsat Map saved: GameData/SCANsat/PluginData/" + filename, 8, ScreenMessageStyle.UPPER_CENTER);
- SCANUtil.SCANlog("Map of [{0}] saved\nMap Size: {1} X {2}\nMinimum Altitude: {3:F0}m; Maximum Altitude: {4:F0}m\nPixel Width At Equator: {5:F6}m", map.Body.displayName.LocalizeBodyName(), map.Map.width, map.Map.height, data.TerrainConfig.MinTerrain, data.TerrainConfig.MaxTerrain, (map.Body.Radius * 2 * Math.PI) / (map.Map.width * 1f));
+ SCANUtil.SCANlog("Map of [{0}] saved\nMap Size: {1} X {2}\nMinimum Altitude: {3:F0}m; Maximum Altitude: {4:F0}m\nPixel Width At Equator: {5:F6}m", map.Body.displayName.LocalizeBodyName(), map.Map.width, map.Map.height, SCANUtil.getTerrainConfig(data).MinTerrain, SCANUtil.getTerrainConfig(data).MaxTerrain, (map.Body.Radius * 2 * Math.PI) / (map.Map.width * 1f));
if (SCAN_Settings_Config.Instance.ExportCSV && map.MType == mapType.Altimetry)
{
diff --git a/SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs b/SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs
index e737f5110..f722ce708 100644
--- a/SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs
+++ b/SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs
@@ -967,7 +967,7 @@ internal static void drawTerrainMap(ref Color32[] pix, ref float[,] values, SCAN
{
if (SCANUtil.isCovered(lon, lat, data, SCANtype.AltimetryHiRes))
{
- c = palette.heightToColor(values[i, j], true, data.TerrainConfig);
+ c = palette.heightToColor(values[i, j], true, SCANUtil.getTerrainConfig(data));
}
else
{
@@ -975,7 +975,7 @@ internal static void drawTerrainMap(ref Color32[] pix, ref float[,] values, SCAN
int ilat = SCANUtil.icLAT(lat);
int lo = ((int)(ilon * scale * 5)) / 5;
int la = ((int)(ilat * scale * 5)) / 5;
- c = palette.heightToColor(values[lo, la], false, data.TerrainConfig);
+ c = palette.heightToColor(values[lo, la], false, SCANUtil.getTerrainConfig(data));
}
c = palette.lerp(c, palette.Clear, 0.1f);
@@ -1189,11 +1189,11 @@ internal static Texture2D drawLoDetailMap(ref Color32[] pix, ref float[,] values
{
if (map.UseCustomRange)
{
- pix[j * width + i] = palette.heightToColor(values[i, j], true, data.TerrainConfig, map.CustomMin, map.CustomMax, map.CustomRange, true);
+ pix[j * width + i] = palette.heightToColor(values[i, j], true, SCANUtil.getTerrainConfig(data), map.CustomMin, map.CustomMax, map.CustomRange, true);
}
else
{
- pix[j * width + i] = palette.heightToColor(values[i, j], true, data.TerrainConfig);
+ pix[j * width + i] = palette.heightToColor(values[i, j], true, SCANUtil.getTerrainConfig(data));
}
}
}
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs
index e211c36dd..538f0081c 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs
@@ -1780,7 +1780,7 @@ public Texture2D LegendImage
switch (bigmap.MType)
{
case mapType.Altimetry:
- return bigmap.MapLegend.getLegend(SCANcontroller.controller.bigMapColor, data.TerrainConfig);
+ return bigmap.MapLegend.getLegend(SCANcontroller.controller.bigMapColor, SCANUtil.getTerrainConfig(data));
case mapType.Biome:
if (body != null && body.BiomeMap != null && body.BiomeMap.Attributes != null)
{
@@ -1968,11 +1968,11 @@ public IList LegendLabels
return null;
}
- string one = string.Format("|\n{0}", (((int)(data.TerrainConfig.MinTerrain / 100)) * 100).ToString("N0"));
+ string one = string.Format("|\n{0}", (((int)(SCANUtil.getTerrainConfig(data).MinTerrain / 10)) * 10).ToString("N0"));
- string two = string.Format("|\n{0}", (((int)((data.TerrainConfig.MinTerrain + (data.TerrainConfig.TerrainRange / 2)) / 100)) * 100).ToString("N0"));
+ string two = string.Format("|\n{0}", (((int)((SCANUtil.getTerrainConfig(data).MinTerrain + (SCANUtil.getTerrainConfig(data).TerrainRange / 2)) / 10)) * 10).ToString("N0"));
- string three = string.Format("|\n{0}", (((int)(data.TerrainConfig.MaxTerrain / 100)) * 100).ToString("N0"));
+ string three = string.Format("|\n{0}", (((int)(SCANUtil.getTerrainConfig(data).MaxTerrain / 10)) * 10).ToString("N0"));
return new List(3) { one, two, three };
}
@@ -2459,7 +2459,7 @@ public string TooltipText(float xPos)
return Localizer.Format(body.BiomeMap.Attributes[current].displayname);
case mapType.Altimetry:
- float terrain = xPos * data.TerrainConfig.TerrainRange + data.TerrainConfig.MinTerrain;
+ float terrain = xPos * SCANUtil.getTerrainConfig(data).TerrainRange + SCANUtil.getTerrainConfig(data).MinTerrain;
return string.Format("{0}m", terrain.ToString("N0"));
}
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_Color.cs b/SCANsat/SCAN_Unity/SCAN_UI_Color.cs
index 877f849c1..4f7d65d6a 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_Color.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_Color.cs
@@ -122,7 +122,7 @@ public void Refresh()
_resourceTransparency = currentResource.Transparency;
}
- currentTerrain = SCANUtil.getData(_terrainPlanet).TerrainConfig;
+ currentTerrain = SCANUtil.getTerrainConfig(_terrainPlanet);
if (currentTerrain != null)
{
@@ -211,7 +211,7 @@ public string TerrainPlanet
_terrainPlanet = body;
- currentTerrain = SCANUtil.getData(body).TerrainConfig;
+ currentTerrain = SCANUtil.getTerrainConfig(body);
if (currentTerrain != null)
{
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
index 317c30459..84367b620 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
@@ -959,11 +959,11 @@ private Color32 getMapPixelColor(MainMapDisplayMode display, int scanline, int i
float val = data.HeightMapValue(data.Body.flightGlobalsIndex, ilon, scanline);
if (SCANUtil.isCovered(ilon, scanline, data, SCANtype.AltimetryHiRes))
{
- c = palette.heightToColor(val, Color, data.TerrainConfig);
+ c = palette.heightToColor(val, Color, SCANUtil.getTerrainConfig(data));
}
else
{
- c = palette.heightToColor(val, false, data.TerrainConfig);
+ c = palette.heightToColor(val, false, SCANUtil.getTerrainConfig(data));
}
return c;
}
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs
index 9b489ed88..03183727c 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs
@@ -1633,7 +1633,7 @@ public Texture2D LegendImage
switch (spotmap.MType)
{
case mapType.Altimetry:
- return spotmap.MapLegend.getLegend(SCANcontroller.controller.zoomMapColor, data.TerrainConfig);
+ return spotmap.MapLegend.getLegend(SCANcontroller.controller.zoomMapColor, SCANUtil.getTerrainConfig(data));
case mapType.Biome:
if (body != null && body.BiomeMap != null && body.BiomeMap.Attributes != null)
{
@@ -1752,11 +1752,11 @@ public IList LegendLabels
return null;
}
- string one = string.Format("|\n{0}", (((int)(terrainMin / 100)) * 100).ToString("N0"));
+ string one = string.Format("|\n{0}", (((int)(terrainMin / 10)) * 10).ToString("N0"));
- string two = string.Format("|\n{0}", (((int)((terrainMin + ((terrainMax - terrainMin) / 2)) / 100)) * 100).ToString("N0"));
+ string two = string.Format("|\n{0}", (((int)((terrainMin + ((terrainMax - terrainMin) / 2)) / 10)) * 10).ToString("N0"));
- string three = string.Format("|\n{0}", (((int)(terrainMax / 100)) * 100).ToString("N0"));
+ string three = string.Format("|\n{0}", (((int)(terrainMax / 10)) * 10).ToString("N0"));
return new List(3) { one, two, three };
}
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index 4abd98698..e347349dc 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -132,6 +132,9 @@ public static SCANcontroller controller
/* Available resources for overlays; loaded from SCANsat configs; only loaded once */
private static DictionaryValueList masterResourceNodes = new DictionaryValueList();
+ /* Terrain height and color option containers loaded from SCANsat configs; only needs to be loaded once */
+ private static Dictionary masterTerrainNodes = new Dictionary();
+
/* List of resources currently loaded from resource addons */
private static List loadedResources = new List();
@@ -327,29 +330,35 @@ public Color32 GetVisualPixel(CelestialBody b, double lon, double lat)
return c;
}
-
- public static List EncodeTerrainConfigs()
+ public static List EncodeTerrainConfigs
{
- List configs = new List();
-
- for (int i = 0; i < FlightGlobals.Bodies.Count; i++)
+ get
{
- CelestialBody b = FlightGlobals.Bodies[i];
-
- if (b == null)
+ try
{
- continue;
+ return masterTerrainNodes.Values.ToList();
}
-
- SCANdata data = SCANUtil.getData(b);
-
- if (data != null)
+ catch (Exception e)
{
- configs.Add(data.TerrainConfig);
+ SCANUtil.SCANlog("Error while saving SCANsat altimetry config data: {0}", e);
}
+
+ return new List();
}
- return configs;
- }
+ }
+
+ public static void setMasterTerrainNodes(List terrainConfigs)
+ {
+ masterTerrainNodes.Clear();
+ try
+ {
+ masterTerrainNodes = terrainConfigs.ToDictionary(a => a.Name, a => a);
+ }
+ catch (Exception e)
+ {
+ SCANUtil.SCANlog("Error while loading SCANsat terrain config settings: {0}", e);
+ }
+ }
public static void checkLoadedTerrainNodes()
@@ -363,17 +372,27 @@ public static void checkLoadedTerrainNodes()
continue;
}
- SCANdata data = SCANUtil.getData(b.bodyName);
- if (data == null)
+ if (SCANUtil.getTerrainConfig(b) == null)
{
- SCANUtil.addToBodyData(b, new SCANdata(b)); // Generates terrain config on init
+ SCANUtil.generateTerrainConfig(b); // Sets the terrain config in the dictionary
}
}
}
+ public static SCANterrainConfig getTerrainNode(string name)
+ {
+ if (masterTerrainNodes.ContainsKey(name))
+ {
+ return masterTerrainNodes[name];
+ }
+
+ SCANUtil.SCANlog("SCANsat terrain config [{0}] cannot be found in master terrain storage list", name);
+ return null;
+ }
+
public static void updateTerrainConfig(SCANterrainConfig t)
{
- SCANterrainConfig update = SCANUtil.getData(t.Name).TerrainConfig;
+ SCANterrainConfig update = getTerrainNode(t.Name);
if (update != null)
{
update.MinTerrain = t.MinTerrain;
@@ -386,23 +405,16 @@ public static void updateTerrainConfig(SCANterrainConfig t)
}
}
- public static void DecodeTerrainConfigs(List configs)
+ public static void addToTerrainConfigData(string name, SCANterrainConfig data)
{
- foreach (SCANterrainConfig config in configs)
+ if (masterTerrainNodes.ContainsKey(name))
{
- if (config == null)
- {
- continue;
- }
-
- SCANdata data = SCANUtil.getData(config.Name);
- if (data == null)
- {
- CelestialBody b = FlightGlobals.Bodies.FirstOrDefault(x => x.bodyName == config.Name);
- SCANUtil.addToBodyData(b, new SCANdata(b)); // Add SCANdata if not present
- }
- updateTerrainConfig(config);
+ Log.Warning($"[{name}] Terrain Config already stored in SCANterrain Data Dictionary");
+ updateTerrainConfig(data);
+ return;
}
+
+ masterTerrainNodes.Add(name, data);
}
public static int MasterResourceCount
@@ -827,19 +839,21 @@ public override void OnSave(ConfigNode node)
node_body.AddValue("LandingTarget", string.Format("{0:N4},{1:N4}", w.Latitude, w.Longitude));
}
}
- if (body_scan.TerrainConfig != null)
+
+ SCANterrainConfig body_config = SCANUtil.getTerrainConfig(body_scan);
+ if (body_config != null)
{
- node_body.AddValue("MinHeightRange", body_scan.TerrainConfig.MinTerrain / body_scan.TerrainConfig.MinHeightMultiplier);
- node_body.AddValue("MaxHeightRange", body_scan.TerrainConfig.MaxTerrain / body_scan.TerrainConfig.MaxHeightMultiplier);
- if (body_scan.TerrainConfig.ClampTerrain != null)
+ node_body.AddValue("MinHeightRange", body_config.MinTerrain / body_config.MinHeightMultiplier);
+ node_body.AddValue("MaxHeightRange", body_config.MaxTerrain / body_config.MaxHeightMultiplier);
+ if (body_config.ClampTerrain != null)
{
- node_body.AddValue("ClampHeight", body_scan.TerrainConfig.ClampTerrain / body_scan.TerrainConfig.ClampHeightMultiplier);
+ node_body.AddValue("ClampHeight", body_config.ClampTerrain / body_config.ClampHeightMultiplier);
}
- node_body.AddValue("PaletteName", body_scan.TerrainConfig.ColorPal.Name);
- node_body.AddValue("PaletteSize", body_scan.TerrainConfig.PalSize);
- node_body.AddValue("PaletteReverse", body_scan.TerrainConfig.PalRev);
- node_body.AddValue("PaletteDiscrete", body_scan.TerrainConfig.PalDis);
+ node_body.AddValue("PaletteName", body_config.ColorPal.Name);
+ node_body.AddValue("PaletteSize", body_config.PalSize);
+ node_body.AddValue("PaletteReverse", body_config.PalRev);
+ node_body.AddValue("PaletteDiscrete", body_config.PalDis);
}
node_body.AddValue("Map", body_scan.shortSerialize());
node_progress.AddNode(node_body);
@@ -2252,17 +2266,12 @@ private void SOIChange(GameEvents.HostedFromToAction VC)
{
body_data.Add(VC.to.bodyName, new SCANdata(VC.to));
}
- }
- private void setNewTerrainConfigValues(SCANterrainConfig terrain, float min, float max, float? clamp, SCANPalette c, int size, bool reverse, bool discrete)
- {
- terrain.MinTerrain = min * terrain.MinHeightMultiplier;
- terrain.MaxTerrain = max * terrain.MaxHeightMultiplier;
- terrain.ClampTerrain = clamp * terrain.ClampHeightMultiplier;
- terrain.ColorPal = c;
- terrain.PalSize = size;
- terrain.PalRev = reverse;
- terrain.PalDis = discrete;
+ // Generate terrain config if it doesn't exist
+ if (SCANUtil.getTerrainConfig(VC.to) == null)
+ {
+ SCANUtil.generateTerrainConfig(VC.to);
+ }
}
private string saveResources(SCANresourceGlobal resource)
diff --git a/SCANsat/SCANutil.cs b/SCANsat/SCANutil.cs
index ccd43f30a..65c75a67e 100644
--- a/SCANsat/SCANutil.cs
+++ b/SCANsat/SCANutil.cs
@@ -267,18 +267,81 @@ public static SCANdata getData(string BodyName)
}
///
- /// For a given Celestial Body name this sets the SCANdata instance with the provided data.
+ /// For a given Celestial Body, this returns the SCANterrainConfig instance if it exists in the SCANcontroller master dictionary; return is null if the SCANterrainConfig does not exist for that body (likely not generated or loaded properly), or if the SCANcontroller Scenario Module has not been loaded.
///
- /// Celestial body (do not use displayName string)
- /// SCANdata instance for the given Celestial Body; null if none exists
- public static void addToBodyData(CelestialBody b, SCANdata data)
+ /// Instance of celestial body
+ /// SCANterrainConfig instance for the given Celestial Body; null if none exists
+ public static SCANterrainConfig getTerrainConfig(CelestialBody body)
+ {
+ return getTerrainConfig(body.bodyName);
+ }
+
+ ///
+ /// For a given SCANdata instance of a celestial body, this returns the SCANterrainConfig instance if it exists in the SCANcontroller master dictionary; return is null if the SCANterrainConfig does not exist for that body (likely not generated or loaded properly), or if the SCANcontroller Scenario Module has not been loaded.
+ ///
+ /// SCANdata instance for a celestial body
+ /// SCANterrainConfig instance for the given Celestial Body; null if none exists
+ public static SCANterrainConfig getTerrainConfig(SCANdata data)
+ {
+ return getTerrainConfig(data.Body.bodyName);
+ }
+
+ ///
+ /// For a given Celestial Body name, this returns the SCANterrainConfig instance if it exists in the SCANcontroller master dictionary; return is null if the SCANterrainConfig does not exist for that body (likely not generated or loaded properly), or if the SCANcontroller Scenario Module has not been loaded.
+ ///
+ /// Name of celestial body (do not use displayName string)
+ /// SCANterrainConfig instance for the given Celestial Body; null if none exists
+ public static SCANterrainConfig getTerrainConfig(string BodyName)
{
if (SCANcontroller.controller == null)
{
- return;
+ return null;
+ }
+
+ return SCANcontroller.getTerrainNode(BodyName);
+ }
+
+ ///
+ /// Generate the default SCANterrainConfig for the provided body and set / update its entry in the masterTerrainNodes.
+ ///
+ /// CelestialBody instance to generate the SCANterrainConfig for
+ /// SCANterrainConfig instance for the given Celestial Body
+ public static SCANterrainConfig generateTerrainConfig(CelestialBody b)
+ {
+ if (b.pqsController == null)
+ {
+ SCANUtil.SCANlog($"[{b.name}] PQS Controller not loaded - no terrain data generated.");
+ return null;
+ }
+
+ float? clamp = null;
+ if (b.ocean)
+ {
+ clamp = 0;
+ }
+
+ float newMin;
+ float newMax;
+
+ try
+ {
+ newMin = ((float)(b.pqsController.radiusMin - b.pqsController.radius)).Mathf_Round(-1);
+ newMax = ((float)(b.pqsController.radiusMax - b.pqsController.radius)).Mathf_Round(-1);
+ if (newMin == newMax)
+ {
+ throw new Exception("Gas Giant / Flat Body"); // Clamp altimetry if body is perfectly smooth / gas giant
+ }
+ }
+ catch (Exception e)
+ {
+ SCANlog($"[{b.name}] Error in calculating Max Height; using default value\n{e}");
+ newMin = SCANconfigLoader.SCANNode.DefaultMinHeightRange;
+ newMax = SCANconfigLoader.SCANNode.DefaultMaxHeightRange;
}
- SCANcontroller.controller.addToBodyData(b, new SCANdata(data));
+ SCANterrainConfig config = new SCANterrainConfig(newMin, newMax, clamp, PaletteLoader(SCANconfigLoader.SCANNode.DefaultPalette, 7), 7, false, false, b);
+ SCANcontroller.addToTerrainConfigData(b.bodyName, config); // Add to masterTerrainNodes dictionary in SCANcontroller
+ return config;
}
///
From a6f374b70e5c1d58743633a4b19be67a9c1c8a28 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Sat, 27 Jun 2026 21:44:42 +1200
Subject: [PATCH 11/20] Generate legend entries to 2sf instead of nearest 100m.
---
SCANsat/SCAN_Map/SCANmapLegend.cs | 38 ++++++++++++++++++++--
SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs | 1 -
SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs | 13 +-------
SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs | 13 +-------
4 files changed, 38 insertions(+), 27 deletions(-)
diff --git a/SCANsat/SCAN_Map/SCANmapLegend.cs b/SCANsat/SCAN_Map/SCANmapLegend.cs
index 9aa00c67e..40de0cf4e 100644
--- a/SCANsat/SCAN_Map/SCANmapLegend.cs
+++ b/SCANsat/SCAN_Map/SCANmapLegend.cs
@@ -103,8 +103,8 @@ public Texture2D getLegend(SCANdata data, bool color, bool stock, CBAttributeMap
int count = biomes.Length;
for (int biome_idx = 0; biome_idx < count; biome_idx++)
{
- int start = (int) Math.Round(biome_idx * 256 / (count * 1d));
- int end = (int) Math.Round((biome_idx + 1) * 256 / (count * 1d));
+ int start = (int)Math.Round(biome_idx * 256 / (count * 1d));
+ int end = (int)Math.Round((biome_idx + 1) * 256 / (count * 1d));
for (int i = start; i < end; i++)
{
@@ -155,5 +155,39 @@ public static Texture2D getStaticLegend(float max, float min, float range, float
t.Apply();
return t;
}
+
+ /// For a given min and max terrain height, generate a set of label strings to 2 significant figures precision between max and min. Used in Big and Zoom maps to display total range.
+ ///
+ /// Value in Minimum Label
+ /// Value in Maximum Label
+ /// IList containing the three legend labels to display.
+ public static IList LegendLabels(double terrainMin, double terrainMax)
+ {
+ int digits = (int)Math.Floor(Math.Log10(terrainMax - terrainMin));
+ int round = (int)Math.Pow(10, digits - 1);
+
+ string one = string.Format("|\n{0}", (((int)Math.Round(terrainMin / round)) * round).ToString("N0"));
+
+ string two = string.Format("|\n{0}", (((int)Math.Round(((terrainMin + terrainMax) / 2) / round)) * round).ToString("N0"));
+
+ string three = string.Format("|\n{0}", (((int)Math.Round(terrainMax / round)) * round).ToString("N0"));
+
+ return new List(3) { one, two, three };
+ }
+
+ ///
+ /// For a given SCANterrainConfig, generate a set of label strings to 3 significant figures precision. Used in Big and Zoom maps to display total range.
+ ///
+ /// SCANterrainConfig instance for a celestial body
+ /// IList containing the three legend labels to display.
+ public static IList LegendLabels(SCANterrainConfig config)
+ {
+ if (config == null)
+ {
+ return null;
+ }
+
+ return LegendLabels(config.MinTerrain, config.MaxTerrain);
+ }
}
}
\ No newline at end of file
diff --git a/SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs b/SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs
index f722ce708..5eb3fb7b8 100644
--- a/SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs
+++ b/SCANsat/SCAN_UI/UI_Framework/SCANuiUtil.cs
@@ -19,7 +19,6 @@
using SCANsat.SCAN_Data;
using SCANsat.SCAN_Map;
using SCANsat.SCAN_Unity;
-using SCANsat.SCAN_UI.UI_Framework;
using palette = SCANsat.SCAN_UI.UI_Framework.SCANcolorUtil;
using UnityEngine;
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs
index 538f0081c..835f79902 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_BigMap.cs
@@ -1963,18 +1963,7 @@ public IList LegendLabels
{
get
{
- if (data == null)
- {
- return null;
- }
-
- string one = string.Format("|\n{0}", (((int)(SCANUtil.getTerrainConfig(data).MinTerrain / 10)) * 10).ToString("N0"));
-
- string two = string.Format("|\n{0}", (((int)((SCANUtil.getTerrainConfig(data).MinTerrain + (SCANUtil.getTerrainConfig(data).TerrainRange / 2)) / 10)) * 10).ToString("N0"));
-
- string three = string.Format("|\n{0}", (((int)(SCANUtil.getTerrainConfig(data).MaxTerrain / 10)) * 10).ToString("N0"));
-
- return new List(3) { one, two, three };
+ return SCANmapLegend.LegendLabels(SCANUtil.getTerrainConfig(body));
}
}
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs
index 03183727c..1ccaf6015 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_ZoomMap.cs
@@ -1747,18 +1747,7 @@ public IList LegendLabels
{
get
{
- if (data == null)
- {
- return null;
- }
-
- string one = string.Format("|\n{0}", (((int)(terrainMin / 10)) * 10).ToString("N0"));
-
- string two = string.Format("|\n{0}", (((int)((terrainMin + ((terrainMax - terrainMin) / 2)) / 10)) * 10).ToString("N0"));
-
- string three = string.Format("|\n{0}", (((int)(terrainMax / 10)) * 10).ToString("N0"));
-
- return new List(3) { one, two, three };
+ return SCANmapLegend.LegendLabels(terrainMin, terrainMax);
}
}
From 61149cd1afbeb14267a4c11635fb2af71532061e Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Sat, 27 Jun 2026 22:04:04 +1200
Subject: [PATCH 12/20] Keep biome colours intact for RGBA defined biomes
---
SCANsat/SCANutil.cs | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/SCANsat/SCANutil.cs b/SCANsat/SCANutil.cs
index 65c75a67e..49665f377 100644
--- a/SCANsat/SCANutil.cs
+++ b/SCANsat/SCANutil.cs
@@ -938,10 +938,8 @@ public static CBAttributeMapSO.MapAttribute[] GetOrBuildPalette(CelestialBody bo
var src = body.BiomeMap.Attributes;
if (!IsGrayscaleEncoded(src))
{
- // Pack ships proper RGBA mapColors. Cache a null sentinel so we
- // never re-detect, and never substitute.
- _scanBiomePaletteCache[body.name] = null;
- return null;
+ _scanBiomePaletteCache[body.name] = body.BiomeMap.Attributes;
+ return body.BiomeMap.Attributes;
}
// Build replacements with categorical hues via golden-ratio HSV
From 3cebe0b32a7c425ed9061cfbffbc16f7d7f889f5 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Tue, 30 Jun 2026 21:41:03 +1200
Subject: [PATCH 13/20] Cache Normal data in alpha channel of Visual data when
caching texture.
---
SCANsat/SCAN_Map/SCANmap.cs | 2 +-
SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs | 2 +-
SCANsat/SCANcontroller.cs | 169 +++++++++++++++++---------
3 files changed, 111 insertions(+), 62 deletions(-)
diff --git a/SCANsat/SCAN_Map/SCANmap.cs b/SCANsat/SCAN_Map/SCANmap.cs
index 62179e662..6cf3545a4 100644
--- a/SCANsat/SCAN_Map/SCANmap.cs
+++ b/SCANsat/SCAN_Map/SCANmap.cs
@@ -1190,7 +1190,7 @@ internal Texture2D getPartialMap(bool apply = true)
lat = Mathf.RoundToInt((float)lat * 256) / 256;
}
- baseColor = SCANcontroller.controller.GetVisualPixel(body, lon, lat);
+ baseColor = SCANcontroller.controller.GetShadedVisualPixel(body, lon, lat);
if (!colorMap || !highResCovered)
{
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
index 84367b620..905cf72f4 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
@@ -999,7 +999,7 @@ private Color32 getMapPixelColor(MainMapDisplayMode display, int scanline, int i
if (highResCovered || lowResCovered)
{
- c = SCANcontroller.controller.GetVisualPixel(data.Body, lon, lat);
+ c = SCANcontroller.controller.GetShadedVisualPixel(data.Body, lon, lat);
if (!highResCovered)
{
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index e347349dc..e44bb0698 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -15,12 +15,9 @@
using Contracts;
using FinePrint.Contracts;
using FinePrint.Contracts.Parameters;
-using FinePrint.Utilities;
-using KSPCommunityLib.Logging;
using KSPTextureLoader;
using SCANsat.SCAN_Data;
using SCANsat.SCAN_Map;
-using SCANsat.SCAN_Palettes;
using SCANsat.SCAN_Platform.Extensions.ConfigNodes;
using SCANsat.SCAN_Reflection;
using SCANsat.SCAN_Toolbar;
@@ -31,7 +28,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
using UnityEngine;
using UnityEngine.Events;
using Log = KSPCommunityLib.Logging.Log;
@@ -156,7 +152,7 @@ public static SCANcontroller controller
private CelestialBody zoomMapBodyVisual;
/* Visual Map Texture Data */
- private Dictionary readableScaledSpaceMaps = new Dictionary();
+ private Dictionary cachedVisualMaps = new Dictionary();
private CelestialBody bigMapBodyScaledSpace;
private CelestialBody zoomMapBodyScaledSpace;
@@ -248,7 +244,7 @@ public bool isVisualTextureLoaded(CelestialBody b)
}
// Check if a texture is present in either dictionary
- if (readableScaledSpaceMaps.GetValueOrDefault(b) != null || memoryMappedVisualMaps.GetValueOrDefault(b) != null)
+ if (cachedVisualMaps.GetValueOrDefault(b) != null || memoryMappedVisualMaps.GetValueOrDefault(b) != null)
{
return true;
}
@@ -256,19 +252,9 @@ public bool isVisualTextureLoaded(CelestialBody b)
return false;
}
- public Color32 GetVisualPixel(CelestialBody b, double lon, double lat)
- {
- Texture2D cachedColorMap = readableScaledSpaceMaps.GetValueOrDefault(b);
- CPUTexture2D mappedColorMap = memoryMappedVisualMaps.GetValueOrDefault(b);
-
- // If no textures are loaded, return static
- if (cachedColorMap == null && mappedColorMap == null)
- {
- return palette.lerp(palette.Black, palette.White, UnityEngine.Random.value);
- }
- // Using doubles because Unity floats apparently default to double and this is faster to just avoid that
- Color32 c = palette.Grey;
+ public Color32 GetShadedVisualPixel(CelestialBody b, double lon, double lat)
+ {
float fLat = ((float)lat + 90f) / 180f;
float fLon = ((float)lon + 270f) / 360f;
@@ -280,53 +266,66 @@ public Color32 GetVisualPixel(CelestialBody b, double lon, double lat)
fLat = Mathf.Clamp01(fLat);
fLon = Mathf.Clamp01(fLon);
+ Color32 c = palette.Grey;
+ double lumOver = 0.5;
+ CPUTexture2D mappedColorMap = memoryMappedVisualMaps.GetValueOrDefault(b);
+ Texture2D cachedColorMap = cachedVisualMaps.GetValueOrDefault(b);
+
+ // If no textures are loaded, return static
if (mappedColorMap != null)
{
c = mappedColorMap.GetPixelBilinear(fLon, fLat);
+ }
+ else if (cachedColorMap != null)
+ {
+ c = cachedColorMap.GetPixelBilinear(fLon, fLat);
+ lumOver = c.a / 255f; // Use alpha channel to store Y axis normal data for cached textures
+ }
+ else
+ {
+ return palette.lerp(palette.Black, palette.White, UnityEngine.Random.value);
+ }
- // Attempt to load Normal map values
- CPUTexture2D mappedNormalMap = memoryMappedNormalMaps.GetValueOrDefault(b);
- if (mappedNormalMap != null) {
- Color32 n = mappedNormalMap.GetPixelBilinear(fLon, fLat);
- float lumOver = n.b / 255f; // Base game KSP blue channel to store Y axis normal data
+ c.a = 255; // Ensure map is fully opaque regardless of source texture alpha
- switch(mappedNormalMap.Format)
- {
- case TextureFormat.BC5:
- lumOver = n.g / 255f; // BC5 stores X in red and Y in green (Z not stored)
- break;
- case TextureFormat.DXT5:
- lumOver = n.g / 255f; // DXT5 stores X in alpha and Y in green (Z not stored)
- break;
- default:
- break;
- }
+ // Attempt to load Normal map values
+ CPUTexture2D mappedNormalMap = memoryMappedNormalMaps.GetValueOrDefault(b);
+ if (mappedNormalMap != null)
+ {
+ Color32 n = mappedNormalMap.GetPixelBilinear(fLon, fLat);
+ // Extract the Y channel from the normal map and normalize to range [0, 1]
+ lumOver = n.b / 255f; // Base game KSP blue channel to store Y axis normal data
- HslColor hslBase = palette.ConvertRgbToHsl(c);
+ switch (mappedNormalMap.Format)
+ {
+ case TextureFormat.BC5:
+ lumOver = n.g / 255f; // BC5 stores X in red and Y in green (Z not stored)
+ break;
+ case TextureFormat.DXT5:
+ lumOver = n.g / 255f; // DXT5 stores X in alpha and Y in green (Z not stored)
+ break;
+ default:
+ break;
+ }
+ }
- double opacity = 0.8;
- double lum = hslBase.L;
+ HslColor hslBase = palette.ConvertRgbToHsl(c);
- if (lum > 0.5d)
- {
- lum = (opacity * (1 - (1 - (2 * (lumOver - 0.5))) * (1 - lum))) + (1 - opacity) * lum;
- }
- else
- {
- lum = (opacity * (2 * lumOver * lum)) + (1 - opacity) * lum;
- }
+ double opacity = 0.8;
+ double lum = hslBase.L;
- c = palette.ConvertHslToRgb(hslBase.H, hslBase.S, lum);
- }
- c.a = 255; // Ensure map is fully opaque regardless of source texture alpha
+ if (lum > 0.5d)
+ {
+ lum = (opacity * (1 - (1 - (2 * (lumOver - 0.5))) * (1 - lum))) + (1 - opacity) * lum;
+ lum = (opacity * lum) + (1 - opacity) * lum;
}
else
{
- c = cachedColorMap.GetPixelBilinear(fLon, fLat);
- c.a = 255; // Ensure map is fully opaque regardless of source texture alpha
+ lum = (opacity * (2 * lumOver * lum)) + (1 - opacity) * lum;
}
+ c = palette.ConvertHslToRgb(hslBase.H, hslBase.S, lum);
return c;
}
@@ -1618,10 +1617,11 @@ internal void LoadVisualMapTexture(CelestialBody b, mapSource s)
}
- void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName)
+ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName, out string normalMapTextureName)
{
material = null;
colorMapTextureName = null;
+ normalMapTextureName = null;
useMaterialForColorMap = true;
if (b.scaledBody == null)
@@ -1642,6 +1642,7 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
if (shaderName == "Terrain/Gas Giant")
{
colorMapTextureName = "_DetailCloudPatternTexture";
+ normalMapTextureName = "_NormalMap";
return;
}
// HapkeScaled is the Sol shader which is also a Parallax-dependent instance
@@ -1654,6 +1655,7 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
string contains_cube = material.HasProperty("_ColorCube") ? "_ColorCube" : null;
SCANUtil.SCANdebugLog($"[{b.name}] Material uses Parallax and contains: {contains_main}, {contains_map}, {contains_cube}.");
colorMapTextureName = "_ColorMap";
+ normalMapTextureName = "_BumpMap";
return;
}
else if (material.HasProperty("_MainTex"))
@@ -1664,17 +1666,27 @@ void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out boo
{
colorMapTextureName = "_ColorMap";
}
+
+ if (material.HasProperty("_BumpMap"))
+ {
+ normalMapTextureName = "_BumpMap";
+ }
+ else if (material.HasProperty("_NormalMap"))
+ {
+ normalMapTextureName = "_NormalMap";
+ }
}
/// Caches visual textures (colour map)
- void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextureName, bool useMaterialForColorMap)
+ void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextureName, string normalMapTextureName, bool useMaterialForColorMap)
{
- if (readableScaledSpaceMaps.GetValueOrDefault(b) != null || memoryMappedVisualMaps.GetValueOrDefault(b) != null)
+ if (cachedVisualMaps.GetValueOrDefault(b) != null || memoryMappedVisualMaps.GetValueOrDefault(b) != null)
{
return; // Already cached
}
Texture2D colorMap = null;
+ Texture2D normalMap = null;
try
{
@@ -1685,7 +1697,6 @@ void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextu
if (sourceColorTexture != null)
{
colorMap = sourceColorTexture.isReadable ? sourceColorTexture : readableTexture(sourceColorTexture, useMaterialForColorMap ? material : null);
- readableScaledSpaceMaps.Add(b, colorMap);
}
else
{
@@ -1693,6 +1704,44 @@ void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextu
return;
}
}
+
+ if (normalMapTextureName == null)
+ {
+ cachedVisualMaps.Add(b, colorMap);
+ return; // No normal map, just cache the color map
+ }
+
+ var sourceNormalTexture = material.GetTexture(normalMapTextureName) as Texture2D;
+ if (sourceNormalTexture != null)
+ {
+ normalMap = sourceNormalTexture.isReadable ? sourceNormalTexture : readableTexture(sourceNormalTexture, null);
+
+ // Get pixel data
+ Color32[] colorPixels = colorMap.GetPixels32();
+ // Merge: Keep RGB from colorPixels, keep A from alphaPixels
+ Color32[] mergedPixels = new Color32[colorPixels.Length];
+
+ // Take the b channel for RGBA textures and g channel for BC5 and DXT5 and shove in alpha of colour
+ for (int i = 0; i < colorPixels.Length; i++)
+ {
+ float u = (float)(i % colorMap.width) / (colorMap.width - 1);
+ float v = (float)(i / colorMap.width) / (colorMap.height - 1);
+ Color32 visualPixel = colorMap.GetPixelBilinear(u, v);
+ Color32 normalPixel = normalMap.GetPixelBilinear(u, v);
+ mergedPixels[i] = new Color32(
+ visualPixel.r,
+ visualPixel.g,
+ visualPixel.b,
+ normalMap.format == TextureFormat.BC5 || normalMap.format == TextureFormat.DXT5 ? normalPixel.g : normalPixel.b
+ );
+ }
+
+ //// Update the existing texture instance with the merged pixel data
+ Texture2D combinedTexture = new Texture2D(colorMap.width, colorMap.height);
+ combinedTexture.SetPixels32(mergedPixels);
+ combinedTexture.Apply();
+ cachedVisualMaps.Add(b, combinedTexture);
+ }
}
catch (Exception e)
{
@@ -1782,7 +1831,7 @@ internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
}
}
- GetVisualMapTexturesForBody(b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName);
+ GetVisualMapTexturesForBody(b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName, out string normalMapTextureName);
if (material == null)
{
@@ -1791,7 +1840,7 @@ internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
else
{
// Load color map
- CacheVisualTexture(b, material, colorMapTextureName, useMaterialForColorMap);
+ CacheVisualTexture(b, material, colorMapTextureName, normalMapTextureName, useMaterialForColorMap);
}
}
@@ -1829,11 +1878,11 @@ internal void UnloadVisualMapTexture(CelestialBody b, mapSource s)
break;
}
- if (readableScaledSpaceMaps.ContainsKey(b))
+ if (cachedVisualMaps.ContainsKey(b))
{
- GameObject.Destroy(readableScaledSpaceMaps[b]);
- readableScaledSpaceMaps[b] = null;
- readableScaledSpaceMaps.Remove(b);
+ GameObject.Destroy(cachedVisualMaps[b]);
+ cachedVisualMaps[b] = null;
+ cachedVisualMaps.Remove(b);
}
if (memoryMappedVisualMaps.ContainsKey(b))
From 59f0403f88a7014ad81fc3300fbf1d25e6aa79f4 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Mon, 6 Jul 2026 16:25:51 +1200
Subject: [PATCH 14/20] Texture management into its own class and drop index
reference in SCANTerrainConfig.
Seriously, why define both name AND index, then overwrite the intuitive one if it doesn't match the other. Just let us define off the name.
---
GameData/SCANsat/Resources/SCANcolors.cfg | 17 -
SCANsat/SCAN_Data/SCANterrainConfig.cs | 13 -
SCANsat/SCANcontroller.cs | 656 +++++++++++-----------
3 files changed, 335 insertions(+), 351 deletions(-)
diff --git a/GameData/SCANsat/Resources/SCANcolors.cfg b/GameData/SCANsat/Resources/SCANcolors.cfg
index 482952849..f5ba69b5f 100644
--- a/GameData/SCANsat/Resources/SCANcolors.cfg
+++ b/GameData/SCANsat/Resources/SCANcolors.cfg
@@ -10,7 +10,6 @@ SCAN_Color_Config
Item
{
name = Kerbin
- index = 1
minHeightRange = -1500
maxHeightRange = 6500
clampHeight = 0
@@ -25,7 +24,6 @@ SCAN_Color_Config
Item
{
name = Sun
- index = 0
minHeightRange = 0
maxHeightRange = 1000
clampHeight = Null
@@ -40,7 +38,6 @@ SCAN_Color_Config
Item
{
name = Mun
- index = 2
minHeightRange = -500
maxHeightRange = 7000
clampHeight = Null
@@ -55,7 +52,6 @@ SCAN_Color_Config
Item
{
name = Minmus
- index = 3
minHeightRange = -500
maxHeightRange = 5500
clampHeight = Null
@@ -70,7 +66,6 @@ SCAN_Color_Config
Item
{
name = Moho
- index = 4
minHeightRange = -300
maxHeightRange = 6500
clampHeight = Null
@@ -85,7 +80,6 @@ SCAN_Color_Config
Item
{
name = Eve
- index = 5
minHeightRange = -2000
maxHeightRange = 7000
clampHeight = 0
@@ -100,7 +94,6 @@ SCAN_Color_Config
Item
{
name = Duna
- index = 6
minHeightRange = 0
maxHeightRange = 7000
clampHeight = Null
@@ -115,7 +108,6 @@ SCAN_Color_Config
Item
{
name = Ike
- index = 7
minHeightRange = 0
maxHeightRange = 12000
clampHeight = Null
@@ -130,7 +122,6 @@ SCAN_Color_Config
Item
{
name = Jool
- index = 8
minHeightRange = 0
maxHeightRange = 1000
clampHeight = Null
@@ -145,7 +136,6 @@ SCAN_Color_Config
Item
{
name = Laythe
- index = 9
minHeightRange = -3000
maxHeightRange = 6000
clampHeight = 0
@@ -160,7 +150,6 @@ SCAN_Color_Config
Item
{
name = Vall
- index = 10
minHeightRange = -500
maxHeightRange = 7500
clampHeight = Null
@@ -175,7 +164,6 @@ SCAN_Color_Config
Item
{
name = Bop
- index = 11
minHeightRange = 2000
maxHeightRange = 21500
clampHeight = Null
@@ -190,7 +178,6 @@ SCAN_Color_Config
Item
{
name = Tylo
- index = 12
minHeightRange = 0
maxHeightRange = 11000
clampHeight = Null
@@ -205,7 +192,6 @@ SCAN_Color_Config
Item
{
name = Gilly
- index = 13
minHeightRange = 1500
maxHeightRange = 6000
clampHeight = Null
@@ -220,7 +206,6 @@ SCAN_Color_Config
Item
{
name = Pol
- index = 14
minHeightRange = 500
maxHeightRange = 5500
clampHeight = Null
@@ -235,7 +220,6 @@ SCAN_Color_Config
Item
{
name = Dres
- index = 15
minHeightRange = -200
maxHeightRange = 5500
clampHeight = Null
@@ -250,7 +234,6 @@ SCAN_Color_Config
Item
{
name = Eeloo
- index = 16
minHeightRange = -500
maxHeightRange = 3500
clampHeight = Null
diff --git a/SCANsat/SCAN_Data/SCANterrainConfig.cs b/SCANsat/SCAN_Data/SCANterrainConfig.cs
index a1e39d189..02c6df7e8 100644
--- a/SCANsat/SCAN_Data/SCANterrainConfig.cs
+++ b/SCANsat/SCAN_Data/SCANterrainConfig.cs
@@ -22,8 +22,6 @@ public class SCANterrainConfig : SCAN_ConfigNodeStorage
[Persistent]
private string name;
[Persistent]
- private int index;
- [Persistent]
private float minHeightRange;
[Persistent]
private float maxHeightRange;
@@ -80,7 +78,6 @@ internal SCANterrainConfig(float min, float max, float? clamp, SCANPalette color
paletteDiscrete = discrete;
body = b;
name = body.bodyName;
- index = body.flightGlobalsIndex;
setDefaultValues();
}
@@ -113,16 +110,6 @@ internal SCANterrainConfig(SCANterrainConfig copy)
public override void OnDecodeFromConfigNode()
{
- body = FlightGlobals.Bodies.FirstOrDefault(b => b.flightGlobalsIndex == index);
- if (body != null)
- {
- name = body.bodyName;
- }
- else
- {
- name = "WrongBody" + index;
- }
-
colorPal = SCANUtil.PaletteLoader(paletteName, paletteSize);
float tempClamp = 0;
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index e44bb0698..05ec2d972 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -35,6 +35,317 @@
namespace SCANsat
{
+ public class SCANtextures
+ {
+ /* Game Loaded Data */
+ public CelestialBody body;
+ public Texture2D cachedVisualMap = null;
+ public Texture2D cachedNormalMap = null;
+
+ /* Memory Mapped Data (requires SCANSAT_BODY_TEXTURES config) */
+ public CPUTexture2D memoryMappedHeightMap = null;
+ public CPUTexture2D memoryMappedNormalMap = null;
+ public CPUTexture2D memoryMappedVisualMap = null;
+
+ public SCANtextures(CelestialBody b)
+ {
+ body = b;
+
+ ConfigNode[] visualOverrides = GameDatabase.Instance.GetConfigNodes("SCANSAT_BODY_TEXTURES");
+
+ for (int i = 0; i < visualOverrides.Length; i++)
+ {
+ ConfigNode node = visualOverrides[i];
+ if (node.HasValue("name"))
+ {
+ string bodyName = node.GetValue("name");
+ if (bodyName == b.name)
+ {
+ GetMemoryMappedTexturesFromConfig(node);
+ break;
+ }
+ }
+ }
+
+ CacheMapTexturesFromBody();
+ SCANUtil.SCANlog(TextureState()); // Log whether using memory mapped or RAM buffered textures
+ }
+
+ private Texture2D readableTexture(Texture tex, Material mat)
+ {
+ if (tex == null)
+ {
+ return null;
+ }
+
+ Texture2D readable = new Texture2D(tex.width, tex.height);
+
+ var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB, 1);
+
+ if (mat != null)
+ {
+ Graphics.Blit(tex, rt, mat);
+ }
+ else
+ {
+ Graphics.Blit(tex, rt);
+ }
+
+ RenderTexture.active = rt;
+
+ readable.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
+
+ RenderTexture.active = null;
+ RenderTexture.ReleaseTemporary(rt);
+
+ rt = null;
+
+ readable.Apply();
+
+ tex = null;
+
+ return readable;
+ }
+
+ public void GetMemoryMappedTexturesFromConfig(ConfigNode node)
+ {
+ string bodyName = node.GetValue("name");
+ if (bodyName == body.name)
+ {
+ string baseFolder = System.IO.Directory.GetParent(KSPUtil.ApplicationRootPath).FullName;
+
+ // Attempt to load heightMap
+ if (memoryMappedHeightMap == null && node.GetValue("heightMap") != null)
+ {
+ string heightMapCPUTextureName = (baseFolder + '/' + node.GetValue("heightMap")).Replace("\\", "/");
+ CPUTextureHandle heightMap = TextureLoader.LoadCPUTexture(heightMapCPUTextureName);
+ if (heightMap.IsError)
+ {
+ Log.Error($"[{body.name}] Height Map Path not defined: {heightMapCPUTextureName}");
+ }
+ else
+ {
+ memoryMappedHeightMap = heightMap.GetTexture();
+ }
+ }
+
+ // Attempt to load normalMap
+ if (memoryMappedNormalMap == null && node.GetValue("normalMap") != null)
+ {
+ string normalMapCPUTextureName = (baseFolder + '/' + node.GetValue("normalMap")).Replace("\\", "/");
+ CPUTextureHandle normalMap = TextureLoader.LoadCPUTexture(normalMapCPUTextureName);
+ if (normalMap.IsError)
+ {
+ Log.Error($"[{body.name}] Normal Map Path not defined: {normalMapCPUTextureName}");
+ }
+ else
+ {
+ memoryMappedNormalMap = normalMap.GetTexture();
+ }
+ }
+
+ // Attempt to load colorMap
+ if (memoryMappedVisualMap == null && node.GetValue("colorMap") != null)
+ {
+ string colorMapCPUTextureName = (baseFolder + '/' + node.GetValue("colorMap")).Replace("\\", "/");
+ CPUTextureHandle colorMap = TextureLoader.LoadCPUTexture(colorMapCPUTextureName);
+ if (colorMap.IsError)
+ {
+ Log.Error($"[{body.name}] Visual Data path not loaded: {colorMapCPUTextureName}");
+ }
+ else
+ {
+ memoryMappedVisualMap = colorMap.GetTexture();
+ }
+ }
+ }
+ }
+
+ public void CacheMapTexturesFromBody()
+ {
+ Material material = null;
+ string colorMapTextureName = null;
+ string normalMapTextureName = null;
+ bool useMaterialForColorMap = true;
+
+ if (body.scaledBody == null)
+ {
+ return;
+ }
+
+ MeshRenderer scaledMesh = body.scaledBody.GetComponent();
+
+ if (scaledMesh == null)
+ {
+ return;
+ }
+
+ material = scaledMesh.sharedMaterial; // TODO: what if there are multiple materials? do we need to check all of them?
+ string shaderName = material.shader.name;
+
+ if (shaderName == "Terrain/Gas Giant")
+ {
+ colorMapTextureName = "_DetailCloudPatternTexture";
+ normalMapTextureName = "_NormalMap";
+ }
+ // HapkeScaled is the Sol shader which is also a Parallax-dependent instance
+ else if (shaderName.Contains("ParallaxScaled") || shaderName.Contains("HapkeScaled"))
+ {
+ SCANparallaxContinued.LoadParallax(body, ref material);
+ useMaterialForColorMap = false;
+ colorMapTextureName = "_ColorMap";
+ normalMapTextureName = "_BumpMap";
+ }
+ else if (material.HasProperty("_MainTex"))
+ {
+ colorMapTextureName = "_MainTex";
+ }
+ else if (material.HasProperty("_ColorMap"))
+ {
+ colorMapTextureName = "_ColorMap";
+ }
+
+ if (material.HasProperty("_BumpMap"))
+ {
+ normalMapTextureName = "_BumpMap";
+ }
+ else if (material.HasProperty("_NormalMap"))
+ {
+ normalMapTextureName = "_NormalMap";
+ }
+
+ // Only cache color / visual map if memory mapped visual map is not available and map not previously cached
+ if (memoryMappedVisualMap == null && cachedVisualMap == null && colorMapTextureName != null)
+ {
+ var sourceColorTexture = material.GetTexture(colorMapTextureName) as Texture2D;
+ if (sourceColorTexture != null)
+ {
+ cachedVisualMap = sourceColorTexture.isReadable ? sourceColorTexture : readableTexture(sourceColorTexture, useMaterialForColorMap ? material : null);
+ }
+ else
+ {
+ Log.Error($"Visual Map cached null texture for body {body.name}, material {material.name} and texture name {colorMapTextureName}");
+ }
+ }
+
+ // Only cache normal map if memory mapped normal map is not available and map not previously cached
+ if (memoryMappedNormalMap == null && cachedNormalMap == null && normalMapTextureName != null)
+ {
+ var sourceNormalTexture = material.GetTexture(normalMapTextureName) as Texture2D;
+ if (sourceNormalTexture != null)
+ {
+ cachedNormalMap = sourceNormalTexture.isReadable ? sourceNormalTexture : readableTexture(sourceNormalTexture, null);
+ }
+ else
+ {
+ Log.Error($"Normal Map cached null texture for body {body.name}, material {material.name} and texture name {normalMapTextureName}");
+ }
+ }
+ }
+
+ public TextureFormat GetNormalFormat()
+ {
+ if (memoryMappedNormalMap != null)
+ {
+ return memoryMappedNormalMap.Format;
+ }
+ else if (cachedNormalMap != null)
+ {
+ return cachedNormalMap.format;
+ }
+ Log.Error($"No normal map textures defined for {body.bodyName}");
+ throw new NullReferenceException($"No normal map textures defined for {body.bodyName}");
+ }
+ public Color32 GetShadedVisualPixel(double lon, double lat)
+ {
+
+ float fLat = ((float)lat + 90f) / 180f;
+ float fLon = ((float)lon + 270f) / 360f;
+
+ if (fLon < 0) { fLon += 1; }
+ if (fLon > 1) { fLon -= 1; }
+ fLon = 1 - fLon;
+
+ fLat = Mathf.Clamp01(fLat);
+ fLon = Mathf.Clamp01(fLon);
+
+ Color32 c = palette.Grey;
+
+ // If no textures are loaded, return static
+ if (memoryMappedVisualMap != null)
+ {
+ c = memoryMappedVisualMap.GetPixelBilinear(fLon, fLat);
+ }
+ else if (cachedVisualMap != null)
+ {
+ c = cachedVisualMap.GetPixelBilinear(fLon, fLat);
+ }
+ else
+ {
+ return palette.lerp(palette.Black, palette.White, UnityEngine.Random.value);
+ }
+
+ // Set c to be fully opaque
+ c.a = 255;
+
+ // Attempt to load Normal map values
+ Color32 n;
+ if (memoryMappedNormalMap != null)
+ {
+ n = memoryMappedNormalMap.GetPixelBilinear(fLon, fLat);
+ }
+ else if (cachedNormalMap != null)
+ {
+ n = cachedNormalMap.GetPixelBilinear(fLon, fLat);
+ }
+ else
+ {
+ return c; // No normal map, return the color as is
+ }
+
+ // Extract the Y channel from the normal map and normalize to range [0, 1]. lumOver of 0.5 is neutral
+ double lumOver = n.b / 255f; // Base game KSP blue channel to store Y axis normal data
+
+ switch (GetNormalFormat())
+ {
+ case TextureFormat.BC5:
+ lumOver = n.g / 255f; // BC5 stores X in red and Y in green (Z not stored)
+ break;
+ case TextureFormat.DXT5:
+ lumOver = n.g / 255f; // DXT5 stores X in alpha and Y in green (Z not stored)
+ break;
+ default:
+ break;
+ }
+
+ HslColor hslBase = palette.ConvertRgbToHsl(c);
+
+ double opacity = 0.8;
+ double lum = hslBase.L;
+
+ if (lum > 0.5d)
+ {
+ lum = (opacity * (1 - (1 - (2 * (lumOver - 0.5))) * (1 - lum))) + (1 - opacity) * lum;
+ lum = (opacity * lum) + (1 - opacity) * lum;
+ }
+ else
+ {
+ lum = (opacity * (2 * lumOver * lum)) + (1 - opacity) * lum;
+ }
+
+ c = palette.ConvertHslToRgb(hslBase.H, hslBase.S, lum);
+ return c;
+ }
+
+ public string TextureState()
+ {
+ string colorMapState = memoryMappedVisualMap != null ? "Memory Mapped" : (cachedVisualMap != null ? "RAM Buffered" : "None");
+ string normalMapState = memoryMappedNormalMap != null ? "Memory Mapped" : (cachedNormalMap != null ? "RAM Buffered" : "None");
+
+ return $"{body.name} Visual Map Texture: {colorMapState}, Normal Map Texture: {normalMapState}";
+ }
+ }
+
[KSPScenario(ScenarioCreationOptions.AddToAllGames | ScenarioCreationOptions.AddToExistingGames, GameScenes.FLIGHT, GameScenes.SPACECENTER, GameScenes.TRACKSTATION)]
public class SCANcontroller : ScenarioModule
{
@@ -152,15 +463,10 @@ public static SCANcontroller controller
private CelestialBody zoomMapBodyVisual;
/* Visual Map Texture Data */
- private Dictionary cachedVisualMaps = new Dictionary();
+ private Dictionary mapTextureHandler = new Dictionary();
private CelestialBody bigMapBodyScaledSpace;
private CelestialBody zoomMapBodyScaledSpace;
- /* Memory Mapped Data (requires SCANSAT_BODY_TEXTURES config) */
- private Dictionary memoryMappedHeightMaps = new Dictionary();
- private Dictionary memoryMappedNormalMaps = new Dictionary();
- private Dictionary memoryMappedVisualMaps = new Dictionary();
-
private SCAN_UI_MainMap _mainMap;
private SCAN_UI_Instruments _instruments;
private SCAN_UI_BigMap _bigMap;
@@ -244,91 +550,31 @@ public bool isVisualTextureLoaded(CelestialBody b)
}
// Check if a texture is present in either dictionary
- if (cachedVisualMaps.GetValueOrDefault(b) != null || memoryMappedVisualMaps.GetValueOrDefault(b) != null)
+ if (mapTextureHandler.GetValueOrDefault(b) != null)
{
- return true;
+ if (mapTextureHandler[b].memoryMappedVisualMap != null || mapTextureHandler[b].cachedVisualMap != null)
+ {
+ return true;
+ }
}
return false;
}
-
public Color32 GetShadedVisualPixel(CelestialBody b, double lon, double lat)
{
-
- float fLat = ((float)lat + 90f) / 180f;
- float fLon = ((float)lon + 270f) / 360f;
-
- if (fLon < 0) { fLon += 1; }
- if (fLon > 1) { fLon -= 1; }
- fLon = 1 - fLon;
-
- fLat = Mathf.Clamp01(fLat);
- fLon = Mathf.Clamp01(fLon);
-
Color32 c = palette.Grey;
- double lumOver = 0.5;
-
- CPUTexture2D mappedColorMap = memoryMappedVisualMaps.GetValueOrDefault(b);
- Texture2D cachedColorMap = cachedVisualMaps.GetValueOrDefault(b);
- // If no textures are loaded, return static
- if (mappedColorMap != null)
- {
- c = mappedColorMap.GetPixelBilinear(fLon, fLat);
- }
- else if (cachedColorMap != null)
+ SCANtextures bodyTextures = mapTextureHandler.GetValueOrDefault(b);
+ if (bodyTextures != null)
{
- c = cachedColorMap.GetPixelBilinear(fLon, fLat);
- lumOver = c.a / 255f; // Use alpha channel to store Y axis normal data for cached textures
- }
- else
- {
- return palette.lerp(palette.Black, palette.White, UnityEngine.Random.value);
+ return bodyTextures.GetShadedVisualPixel(lon, lat);
}
- c.a = 255; // Ensure map is fully opaque regardless of source texture alpha
-
- // Attempt to load Normal map values
- CPUTexture2D mappedNormalMap = memoryMappedNormalMaps.GetValueOrDefault(b);
- if (mappedNormalMap != null)
- {
- Color32 n = mappedNormalMap.GetPixelBilinear(fLon, fLat);
- // Extract the Y channel from the normal map and normalize to range [0, 1]
- lumOver = n.b / 255f; // Base game KSP blue channel to store Y axis normal data
-
- switch (mappedNormalMap.Format)
- {
- case TextureFormat.BC5:
- lumOver = n.g / 255f; // BC5 stores X in red and Y in green (Z not stored)
- break;
- case TextureFormat.DXT5:
- lumOver = n.g / 255f; // DXT5 stores X in alpha and Y in green (Z not stored)
- break;
- default:
- break;
- }
- }
-
- HslColor hslBase = palette.ConvertRgbToHsl(c);
-
- double opacity = 0.8;
- double lum = hslBase.L;
-
- if (lum > 0.5d)
- {
- lum = (opacity * (1 - (1 - (2 * (lumOver - 0.5))) * (1 - lum))) + (1 - opacity) * lum;
- lum = (opacity * lum) + (1 - opacity) * lum;
- }
- else
- {
- lum = (opacity * (2 * lumOver * lum)) + (1 - opacity) * lum;
- }
-
- c = palette.ConvertHslToRgb(hslBase.H, hslBase.S, lum);
return c;
}
+
public static List EncodeTerrainConfigs
{
get
@@ -409,7 +655,6 @@ public static void addToTerrainConfigData(string name, SCANterrainConfig data)
if (masterTerrainNodes.ContainsKey(name))
{
Log.Warning($"[{name}] Terrain Config already stored in SCANterrain Data Dictionary");
- updateTerrainConfig(data);
return;
}
@@ -1617,138 +1862,6 @@ internal void LoadVisualMapTexture(CelestialBody b, mapSource s)
}
- void GetVisualMapTexturesForBody(CelestialBody b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName, out string normalMapTextureName)
- {
- material = null;
- colorMapTextureName = null;
- normalMapTextureName = null;
- useMaterialForColorMap = true;
-
- if (b.scaledBody == null)
- {
- return;
- }
-
- MeshRenderer scaledMesh = b.scaledBody.GetComponent();
-
- if (scaledMesh == null)
- {
- return;
- }
-
- material = scaledMesh.sharedMaterial; // TODO: what if there are multiple materials? do we need to check all of them?
- string shaderName = material.shader.name;
-
- if (shaderName == "Terrain/Gas Giant")
- {
- colorMapTextureName = "_DetailCloudPatternTexture";
- normalMapTextureName = "_NormalMap";
- return;
- }
- // HapkeScaled is the Sol shader which is also a Parallax-dependent instance
- else if (shaderName.Contains("ParallaxScaled") || shaderName.Contains("HapkeScaled"))
- {
- SCANparallaxContinued.LoadParallax(b, ref material);
- useMaterialForColorMap = false;
- string contains_main = material.HasProperty("_MainTex") ? " _MainTex" : null;
- string contains_map = material.HasProperty("_ColorMap") ? "_ColorMap" : null;
- string contains_cube = material.HasProperty("_ColorCube") ? "_ColorCube" : null;
- SCANUtil.SCANdebugLog($"[{b.name}] Material uses Parallax and contains: {contains_main}, {contains_map}, {contains_cube}.");
- colorMapTextureName = "_ColorMap";
- normalMapTextureName = "_BumpMap";
- return;
- }
- else if (material.HasProperty("_MainTex"))
- {
- colorMapTextureName = "_MainTex";
- }
- else if (material.HasProperty("_ColorMap"))
- {
- colorMapTextureName = "_ColorMap";
- }
-
- if (material.HasProperty("_BumpMap"))
- {
- normalMapTextureName = "_BumpMap";
- }
- else if (material.HasProperty("_NormalMap"))
- {
- normalMapTextureName = "_NormalMap";
- }
- }
-
- /// Caches visual textures (colour map)
- void CacheVisualTexture(CelestialBody b, Material material, string colorMapTextureName, string normalMapTextureName, bool useMaterialForColorMap)
- {
- if (cachedVisualMaps.GetValueOrDefault(b) != null || memoryMappedVisualMaps.GetValueOrDefault(b) != null)
- {
- return; // Already cached
- }
-
- Texture2D colorMap = null;
- Texture2D normalMap = null;
-
- try
- {
- // Load color map
- if (colorMapTextureName != null)
- {
- var sourceColorTexture = material.GetTexture(colorMapTextureName) as Texture2D;
- if (sourceColorTexture != null)
- {
- colorMap = sourceColorTexture.isReadable ? sourceColorTexture : readableTexture(sourceColorTexture, useMaterialForColorMap ? material : null);
- }
- else
- {
- Log.Error($"GetTexture returned a null texture for body {b.name}, material {material.name} and texture name {colorMapTextureName}");
- return;
- }
- }
-
- if (normalMapTextureName == null)
- {
- cachedVisualMaps.Add(b, colorMap);
- return; // No normal map, just cache the color map
- }
-
- var sourceNormalTexture = material.GetTexture(normalMapTextureName) as Texture2D;
- if (sourceNormalTexture != null)
- {
- normalMap = sourceNormalTexture.isReadable ? sourceNormalTexture : readableTexture(sourceNormalTexture, null);
-
- // Get pixel data
- Color32[] colorPixels = colorMap.GetPixels32();
- // Merge: Keep RGB from colorPixels, keep A from alphaPixels
- Color32[] mergedPixels = new Color32[colorPixels.Length];
-
- // Take the b channel for RGBA textures and g channel for BC5 and DXT5 and shove in alpha of colour
- for (int i = 0; i < colorPixels.Length; i++)
- {
- float u = (float)(i % colorMap.width) / (colorMap.width - 1);
- float v = (float)(i / colorMap.width) / (colorMap.height - 1);
- Color32 visualPixel = colorMap.GetPixelBilinear(u, v);
- Color32 normalPixel = normalMap.GetPixelBilinear(u, v);
- mergedPixels[i] = new Color32(
- visualPixel.r,
- visualPixel.g,
- visualPixel.b,
- normalMap.format == TextureFormat.BC5 || normalMap.format == TextureFormat.DXT5 ? normalPixel.g : normalPixel.b
- );
- }
-
- //// Update the existing texture instance with the merged pixel data
- Texture2D combinedTexture = new Texture2D(colorMap.width, colorMap.height);
- combinedTexture.SetPixels32(mergedPixels);
- combinedTexture.Apply();
- cachedVisualMaps.Add(b, combinedTexture);
- }
- }
- catch (Exception e)
- {
- Log.Error($"Error caching visual texture from materials for body {b.name}: {e}");
- }
- }
-
internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
{
if (!SCAN_Settings_Config.Instance.VisibleMapsActive)
@@ -1771,77 +1884,13 @@ internal void LoadVisualMapTexture_Renamed(CelestialBody b, mapSource s)
break;
}
- if (memoryMappedVisualMaps.GetValueOrDefault(b) != null)
+ if (mapTextureHandler.GetValueOrDefault(b) != null)
{
return; // Already cached
}
- ConfigNode[] visualOverrides = GameDatabase.Instance.GetConfigNodes("SCANSAT_BODY_TEXTURES");
-
- for (int i = 0; i < visualOverrides.Length; i++)
- {
- ConfigNode node = visualOverrides[i];
- if (node.HasValue("name") && node.HasValue("colorMap"))
- {
- string bodyName = node.GetValue("name");
- if (bodyName == b.name)
- {
- string baseFolder = System.IO.Directory.GetParent(KSPUtil.ApplicationRootPath).FullName;
-
- // Attempt to load heightMap
- if (!memoryMappedHeightMaps.ContainsKey(b))
- {
- string heightMapCPUTextureName = (baseFolder + '/' + node.GetValue("heightMap")).Replace("\\", "/");
- CPUTextureHandle heightMap = TextureLoader.LoadCPUTexture(heightMapCPUTextureName);
- if (heightMap.IsError)
- {
- Log.Error($"[{b.name}] Height Map Path not defined: {heightMapCPUTextureName}");
- return;
- }
- memoryMappedHeightMaps.Add(b, heightMap.GetTexture());
- }
-
- // Attempt to load normalMap
- if (!memoryMappedNormalMaps.ContainsKey(b))
- {
- string normalMapCPUTextureName = (baseFolder + '/' + node.GetValue("normalMap")).Replace("\\", "/");
- CPUTextureHandle normalMap = TextureLoader.LoadCPUTexture(normalMapCPUTextureName);
- if (normalMap.IsError)
- {
- Log.Error($"[{b.name}] Normal Map Path not defined: {normalMapCPUTextureName}");
- return;
- }
- memoryMappedNormalMaps.Add(b, normalMap.GetTexture());
- }
-
- // Attempt to load colorMap
- if (!memoryMappedVisualMaps.ContainsKey(b))
- {
- string colorMapCPUTextureName = (baseFolder + '/' + node.GetValue("colorMap")).Replace("\\", "/");
- CPUTextureHandle colorMap = TextureLoader.LoadCPUTexture(colorMapCPUTextureName);
- if (colorMap.IsError)
- {
- Log.Error($"[{b.name}] Visual Data path not loaded: {colorMapCPUTextureName}");
- return;
- }
- memoryMappedVisualMaps.Add(b, colorMap.GetTexture());
- }
- return;
- }
- }
- }
-
- GetVisualMapTexturesForBody(b, out Material material, out bool useMaterialForColorMap, out string colorMapTextureName, out string normalMapTextureName);
-
- if (material == null)
- {
- Log.Error($"GetVisualMapTexturesForBody returned a null material for body {b.name}");
- }
- else
- {
- // Load color map
- CacheVisualTexture(b, material, colorMapTextureName, normalMapTextureName, useMaterialForColorMap);
- }
+ // Load the textures for this body (either memory mapped, or RAM cached if memory mapping not available)
+ mapTextureHandler.Add(b, new SCANtextures(b));
}
internal void UnloadVisualMapTexture(CelestialBody b, mapSource s)
@@ -1878,55 +1927,20 @@ internal void UnloadVisualMapTexture(CelestialBody b, mapSource s)
break;
}
- if (cachedVisualMaps.ContainsKey(b))
+ if (mapTextureHandler.ContainsKey(b))
{
- GameObject.Destroy(cachedVisualMaps[b]);
- cachedVisualMaps[b] = null;
- cachedVisualMaps.Remove(b);
- }
-
- if (memoryMappedVisualMaps.ContainsKey(b))
- {
- // TODO: Do I need to destroy these textures as well?
- memoryMappedVisualMaps[b] = null;
- memoryMappedVisualMaps.Remove(b);
- }
- }
-
- private Texture2D readableTexture(Texture tex, Material mat)
- {
- if (tex == null)
- {
- return null;
- }
-
- Texture2D readable = new Texture2D(tex.width, tex.height);
-
- var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB, 1);
+ if (mapTextureHandler[b].cachedVisualMap != null)
+ {
+ GameObject.Destroy(mapTextureHandler[b].cachedVisualMap);
+ }
+ if (mapTextureHandler[b].cachedNormalMap != null)
+ {
+ GameObject.Destroy(mapTextureHandler[b].cachedNormalMap);
+ }
- if (mat != null)
- {
- Graphics.Blit(tex, rt, mat);
+ mapTextureHandler[b] = null;
+ mapTextureHandler.Remove(b);
}
- else
- {
- Graphics.Blit(tex, rt);
- }
-
- RenderTexture.active = rt;
-
- readable.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
-
- RenderTexture.active = null;
- RenderTexture.ReleaseTemporary(rt);
-
- rt = null;
-
- readable.Apply();
-
- tex = null;
-
- return readable;
}
private void OnGUI()
From 10cb442bc5e0ae64773706edb136b5188cc4c420 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Tue, 7 Jul 2026 14:05:50 +1200
Subject: [PATCH 15/20] Updated README to contain some information about what
the hell I have done.
---
README.md | 39 ++++++++++++++++++++++++++++++---------
1 file changed, 30 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 9647d41c6..1d620613d 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,8 @@
* [1. Installation and Interoperability][1]
* [a. Installation][1a]
* [b. GameData Layout][1b]
- * [c. Other Add-Ons][1c]
+ * [c. Texture Configurations][1c]
+ * [d. Other Add-Ons][1d]
* [2. Types of Scans][2]
* [a. Native SCANsat][2a]
* [3. Resource Scanning][3]
@@ -165,7 +166,25 @@ For licensing information, please see the [included LICENSE.txt][SCANsat:rel-lic
![][SCANsat:gamedata]
-#### [:top:][top] 1c. Other Add-Ons
+#### [:top:][top] 1c. Texture Configurations
+
+S.C.A.N. was originally designed to load all visual and normal textures into RAM directly from the game, as this doesn't require any additional configuration steps for planet packs to integrate directly with the mod. This, however, has led to performance issues with large scale textures and planet packs (notoriously Real Solar System scale packs like Sol).
+
+To get around this, support has been added for memory mapping external textures through KSPTextureLoader. This works by defining the relative path to the height, normal and color textures for each planet, and defining them in a config file as shown below (recommend making one for each body titled "BodyName_SCANsat.cfg"):
+
+```
+SCANSAT_BODY_TEXTURES
+{
+ name = Mercury // Use name of body in Kopernicus
+ heightMap = GameData/Sol-Textures/PluginData/01_Mercury/Kopernicus/Mercury_Height.dds
+ normalMap = GameData/Sol-Textures/PluginData/01_Mercury/Kopernicus/Mercury_Normal.dds
+ colorMap = GameData/Sol-Textures/PluginData/01_Mercury/Kopernicus/Mercury_Color.dds
+}
+```
+
+With these paths defined, the CPU is able to pull data directly out of the texture from the file system without loading it into RAM. Planet pack makers are encouraged to use this new interface with their packs as this directly specifies the textures SCANsat will display in game.
+
+#### [:top:][top] 1d. Other Add-Ons
S.C.A.N. is proud to collaborate with other KSP mods and modding teams. Following is a table of all of the mods, add-ons, or software that we interoperate with.
@@ -175,12 +194,9 @@ S.C.A.N. is proud to collaborate with other KSP mods and modding teams. Followin
[**Blizzy78's Toolbar**][toolbar:release] | [![Support for Karbonite][karbonite:logo]][karbonite:release]
[**ModuleManager**][mm:release] | [![Support for ALCOR][alcor:logo]][alcor:release]
[**RasterPropMonitor**][rpm:release] |
+[**KSPTextureLoader**][ksptl:release] |
-* **SCANsat**
- * [x] [**v16.0**][SCANsat:rel-thread] SCANsat Release **version: v16.0**
- * [x] [**v15.0**][SCANsat:dev-thread] SCANsat Dev **version: v15.0**
-
- **MM**, **RPM**, **MechJeb**, and **Toolbar** are all **soft** dependencies. This means your experience with SCANsat will be enhanced if you are using these mods, but they are not necessary.
+ **MM**, **RPM**, **MechJeb**, **KSPTextureLoader** and **Toolbar** are all **soft** dependencies. This means your experience with SCANsat will be enhanced if you are using these mods, but they are not necessary.
**SCANsat** is built against the following mods:
* [x] [![][shield:support-mm]][mm:release]
@@ -200,6 +216,8 @@ scanning modules or parts).
* **RadarHi**: Advanced, High-Resolution RADAR Altimetry (in color, unlimited zoom)
* **Slope**: Slope Data converted from RADAR data
* **Biome**: Biome Detection and Classification (in color, unlimited zoom)
+ * **VisualLo**: Basic, Low-Resolution Visual Imaging (b&w, limited zoom)
+ * **VisualHi**: Full Color, High Resolution Visual Imaging
* **Anomaly**: Anomaly Detection and Labeling
* **Resource**: Scan for chemical or mineral resource on the surface.
@@ -815,7 +833,8 @@ sneaky then they can of course be sneaky.
[1]: #top-1-installation-and-interoperability
[1a]: #top-1a-installation
[1b]: #top-1b-gamedata-layout
-[1c]: #top-1c-other-add-ons
+[1c]: #top-1c-texture-configurations
+[1d]: #top-1d-other-add-ons
[2]: #top-2-types-of-scans
[2a]: #top-2a-scansat-scans
[3]: #top-3-resource-scanning
@@ -961,7 +980,9 @@ sneaky then they can of course be sneaky.
[toolbar:release]: http://forum.kerbalspaceprogram.com/threads/60863
-[rpm:release]: http://forum.kerbalspaceprogram.com/threads/117471
+[rpm:release]: https://github.com/FirstPersonKSP/RasterPropMonitor/releases
+
+[ksptl:release]: https://github.com/Phantomical/KSPTextureLoader/releases
[cconfig:release]: http://forum.kerbalspaceprogram.com/threads/101604
From 585ff4577f26b5a4e4d4c5157766880ace6a2113 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Tue, 7 Jul 2026 15:07:20 +1200
Subject: [PATCH 16/20] Add readme shields to better match existing formatting.
---
README.md | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 1d620613d..096456887 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@
> ![scan your planetoid like the big boys do][bigmap-scan-10000x]
> ###### **Example SAR scan of Kerbin at 1000x and then 10,000x warp**
+[![][shield:support-ksptl]][ksptl:release]
[![][shield:support-rpm]][RPM:release]
[![][shield:support-mm]][mm:release]
[![][shield:support-toolbar]][toolbar:release]
@@ -196,9 +197,12 @@ S.C.A.N. is proud to collaborate with other KSP mods and modding teams. Followin
[**RasterPropMonitor**][rpm:release] |
[**KSPTextureLoader**][ksptl:release] |
- **MM**, **RPM**, **MechJeb**, **KSPTextureLoader** and **Toolbar** are all **soft** dependencies. This means your experience with SCANsat will be enhanced if you are using these mods, but they are not necessary.
+ **MM**, **RPM**, **MechJeb** and **Toolbar** are all **soft** dependencies. This means your experience with SCANsat will be enhanced if you are using these mods, but they are not necessary.
+
+**KSPTextureLoader** is a built dependency, however, as all planets that have external texture files are strongly recommended to use memory mapped textures, with RAM cached variants only being a fallback. This ensures that no errors occur with shader configs and that the exact texture desired ends up displayed in game.
**SCANsat** is built against the following mods:
+ * [x] [![][shield:support-ksptl]][ksptl:release]
* [x] [![][shield:support-mm]][mm:release]
* [x] [![][shield:support-rpm]][rpm:release]
* [x] [![][shield:support-alcor]][alcor:release]
@@ -882,8 +886,9 @@ sneaky then they can of course be sneaky.
[shield:jenkins-dev]: http://img.shields.io/jenkins/s/https/ksp.sarbian.com/jenkins/SCANsat-dev.svg
[shield:jenkins-rel]: http://img.shields.io/jenkins/s/https/ksp.sarbian.com/jenkins/SCANsat-release.svg
-[shield:support-ksp]: http://img.shields.io/badge/for%20KSP-v1.4.1-bad455.svg
+[shield:support-ksp]: http://img.shields.io/badge/for%20KSP-v1.12.x-bad455.svg
[shield:support-rpm]: http://img.shields.io/badge/works%20with%20RPM-v0.28.x-a31f34.svg
+[shield:support-ksptl]: https://img.shields.io/badge/requires%20KSPTextureLoader-1.0.34+-84e0d7.svg
[shield:support-mm]: http://img.shields.io/badge/works%20with%20MM-v3.x-40b7c0.svg
[shield:support-toolbar]: http://img.shields.io/badge/works%20with%20Blizzy's%20Toolbar-1.7.x-7c69c0.svg
[shield:support-alcor]: http://img.shields.io/badge/works%20with%20ALCOR-0.9-299bc7.svg
From ab172052779fee7b9b95a448c51e3f1edb25e4e1 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Wed, 8 Jul 2026 08:32:09 +1200
Subject: [PATCH 17/20] Switch to single reflect check on MechJeb and add
texture reload for minimap.
---
SCANmechjeb/SCANmechjeb.cs | 41 +++++++++++++++++---------
SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs | 4 +++
SCANsat/SCANcontroller.cs | 42 +++++++++++++++------------
3 files changed, 55 insertions(+), 32 deletions(-)
diff --git a/SCANmechjeb/SCANmechjeb.cs b/SCANmechjeb/SCANmechjeb.cs
index 579bd0f4f..5d57023d1 100644
--- a/SCANmechjeb/SCANmechjeb.cs
+++ b/SCANmechjeb/SCANmechjeb.cs
@@ -19,6 +19,7 @@
using UnityEngine;
using UnityEngine.Events;
+using System.Reflection;
namespace SCANmechjeb
{
@@ -35,23 +36,41 @@ class SCANmechjeb : MonoBehaviour
private Vector2d coords = new Vector2d();
private bool shutdown, mjOnboard, mjTechTreeLocked;
- #region Helpers
+ // Store references to required fields - type name changes between Target / Hidden and target / hidden across MechJeb versions
+ private static FieldInfo targetField;
+ private static FieldInfo hiddenField;
///
- /// Reflectively fetches MechJebCore's target controller, tolerating the
+ /// Reflectively fetches MechJebCore fields, tolerating the
/// 'Target'/'target' field rename across MechJeb versions. Returns null
/// (and logs) if the field can't be resolved under any known name.
///
- private static MechJebModuleTargetController GetMJCoreTarget(MechJebCore mjc)
+ static SCANmechjeb()
{
- var t = mjc.GetType();
- var field = t.GetField("Target") ?? t.GetField("target");
- if (field == null)
+ var t = new MechJebCore().GetType();
+ targetField = t.GetField("Target") ?? t.GetField("target");
+ if (targetField == null)
{
Log.Message("MechJebCore 'target' field could not be found under any known name; MechJeb support broken.");
}
- return field?.GetValue(mjc) as MechJebModuleTargetController;
+ hiddenField = t.GetField("Hidden") ?? t.GetField("hidden");
+ if (hiddenField == null)
+ {
+ Log.Message("MechJebGuidanceModule 'hidden' field could not be found under any known name; MechJeb support broken.");
+ }
+ }
+
+ #region Helpers
+
+ ///
+ /// Reflectively fetches MechJebCore's target controller, tolerating the
+ /// 'Target'/'target' field rename across MechJeb versions. Returns null
+ /// (and logs) if the field can't be resolved under any known name.
+ ///
+ private static MechJebModuleTargetController GetMJCoreTarget(MechJebCore mjc)
+ {
+ return targetField?.GetValue(mjc) as MechJebModuleTargetController;
}
///
@@ -61,13 +80,7 @@ private static MechJebModuleTargetController GetMJCoreTarget(MechJebCore mjc)
///
private static bool? IsMJGuidanceModuleHidden(DisplayModule gm)
{
- var t = gm.GetType();
- var field = t.GetField("Hidden") ?? t.GetField("hidden");
- if (field == null)
- {
- Log.Message("MechJebGuidanceModule 'hidden' field could not be found under any known name; MechJeb support broken.");
- }
- return field?.GetValue(gm) as bool?;
+ return hiddenField?.GetValue(gm) as bool?;
}
#endregion
diff --git a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
index 905cf72f4..ffe870c3b 100644
--- a/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
+++ b/SCANsat/SCAN_Unity/SCAN_UI_MainMap.cs
@@ -13,6 +13,7 @@
using KSP.UI;
using SCANsat.SCAN_Data;
+using SCANsat.SCAN_Map;
using SCANsat.SCAN_Toolbar;
using SCANsat.SCAN_UI.UI_Framework;
using SCANsat.Unity;
@@ -107,6 +108,7 @@ private void soiChange(GameEvents.HostedFromToAction VC)
v = FlightGlobals.ActiveVessel;
data = SCANUtil.getData(v.mainBody);
+ SCANcontroller.controller.LoadVisualMapTexture_Renamed(v.mainBody, mapSource.Data);
if (data == null)
{
@@ -137,6 +139,7 @@ private void RefreshAfterVesselChanged()
// TODO: make this smarter about not doing work if it doesn't need to
data = SCANUtil.getData(v.mainBody);
+ SCANcontroller.controller.LoadVisualMapTexture_Renamed(v.mainBody, mapSource.Data);
if (data == null)
{
@@ -389,6 +392,7 @@ public void Open()
v = FlightGlobals.ActiveVessel;
data = SCANUtil.getData(v.mainBody);
+ SCANcontroller.controller.LoadVisualMapTexture_Renamed(v.mainBody, mapSource.Data);
if (data == null)
{
diff --git a/SCANsat/SCANcontroller.cs b/SCANsat/SCANcontroller.cs
index 05ec2d972..649bb1247 100644
--- a/SCANsat/SCANcontroller.cs
+++ b/SCANsat/SCANcontroller.cs
@@ -115,47 +115,53 @@ public void GetMemoryMappedTexturesFromConfig(ConfigNode node)
string baseFolder = System.IO.Directory.GetParent(KSPUtil.ApplicationRootPath).FullName;
// Attempt to load heightMap
- if (memoryMappedHeightMap == null && node.GetValue("heightMap") != null)
+ string heightMapPath = node.GetValue("heightMap");
+ if (memoryMappedHeightMap == null && heightMapPath != null)
{
- string heightMapCPUTextureName = (baseFolder + '/' + node.GetValue("heightMap")).Replace("\\", "/");
+ string heightMapCPUTextureName = (baseFolder + '/' + heightMapPath).Replace("\\", "/");
CPUTextureHandle heightMap = TextureLoader.LoadCPUTexture(heightMapCPUTextureName);
- if (heightMap.IsError)
+ try
{
- Log.Error($"[{body.name}] Height Map Path not defined: {heightMapCPUTextureName}");
+ memoryMappedHeightMap = heightMap.GetTexture();
}
- else
+ catch (Exception e)
{
- memoryMappedHeightMap = heightMap.GetTexture();
+ Log.Error($"[{body.name}] Height Map Path not defined: {heightMapCPUTextureName}");
+ Log.Exception(e);
}
}
// Attempt to load normalMap
- if (memoryMappedNormalMap == null && node.GetValue("normalMap") != null)
+ string normalMapPath = node.GetValue("normalMap");
+ if (memoryMappedNormalMap == null && normalMapPath != null)
{
- string normalMapCPUTextureName = (baseFolder + '/' + node.GetValue("normalMap")).Replace("\\", "/");
+ string normalMapCPUTextureName = (baseFolder + '/' + normalMapPath).Replace("\\", "/");
CPUTextureHandle normalMap = TextureLoader.LoadCPUTexture(normalMapCPUTextureName);
- if (normalMap.IsError)
+ try
{
- Log.Error($"[{body.name}] Normal Map Path not defined: {normalMapCPUTextureName}");
+ memoryMappedNormalMap = normalMap.GetTexture();
}
- else
+ catch (Exception e)
{
- memoryMappedNormalMap = normalMap.GetTexture();
+ Log.Error($"[{body.name}] Normal Map Path not defined: {normalMapCPUTextureName}");
+ Log.Exception(e);
}
}
// Attempt to load colorMap
- if (memoryMappedVisualMap == null && node.GetValue("colorMap") != null)
+ string visualMapPath = node.GetValue("colorMap");
+ if (memoryMappedVisualMap == null && visualMapPath != null)
{
- string colorMapCPUTextureName = (baseFolder + '/' + node.GetValue("colorMap")).Replace("\\", "/");
+ string colorMapCPUTextureName = (baseFolder + '/' + visualMapPath).Replace("\\", "/");
CPUTextureHandle colorMap = TextureLoader.LoadCPUTexture(colorMapCPUTextureName);
- if (colorMap.IsError)
+ try
{
- Log.Error($"[{body.name}] Visual Data path not loaded: {colorMapCPUTextureName}");
+ memoryMappedVisualMap = colorMap.GetTexture();
}
- else
+ catch (Exception e)
{
- memoryMappedVisualMap = colorMap.GetTexture();
+ Log.Error($"[{body.name}] Visual Data path not loaded: {colorMapCPUTextureName}");
+ Log.Exception(e);
}
}
}
From 386f51d7340feb88ba8966e469fe3415d4cc648a Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Wed, 8 Jul 2026 16:41:21 +1200
Subject: [PATCH 18/20] Update build pipeline to KSPBuildTools 1.1.1
---
.github/workflows/build.yml | 2 +-
.github/workflows/create-release.yml | 2 +-
.github/workflows/publish-to-spacedock.yml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d44b36e12..925fb34ba 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -17,6 +17,6 @@ on:
jobs:
build:
- uses: KSPModdingLibs/KSPBuildTools/.github/workflows/build.yml@0.0.4
+ uses: KSPModdingLibs/KSPBuildTools/.github/workflows/build.yml@1.1.1
with:
use-ckan: true
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index 0094e516e..fb45e092c 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -9,7 +9,7 @@ on:
jobs:
create-release:
- uses: KSPModdingLibs/KSPBuildTools/.github/workflows/create-release.yml@0.0.4
+ uses: KSPModdingLibs/KSPBuildTools/.github/workflows/create-release.yml@1.1.1
with:
version-string: ${{ inputs.version-string }}
use-ckan: true
diff --git a/.github/workflows/publish-to-spacedock.yml b/.github/workflows/publish-to-spacedock.yml
index 183d1fbe5..d1b0a0ae7 100644
--- a/.github/workflows/publish-to-spacedock.yml
+++ b/.github/workflows/publish-to-spacedock.yml
@@ -6,7 +6,7 @@ on:
jobs:
publish-to-spacedock:
- uses: KSPModdingLibs/KSPBuildTools/.github/workflows/publish-to-spacedock.yml@0.0.4
+ uses: KSPModdingLibs/KSPBuildTools/.github/workflows/publish-to-spacedock.yml@1.1.1
with:
spacedock-username: ${{ vars.SPACEDOCK_USERNAME }}
mod-id: ${{ vars.SPACEDOCK_MOD_ID }}
From f053d9d62c185547e14ca3c08146a8548ea0e71d Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Wed, 8 Jul 2026 19:34:34 +1200
Subject: [PATCH 19/20] Readonly fields and update main map button type from
Terrain to Altimetry to match large map.
---
SCANmechjeb/SCANmechjeb.cs | 4 ++--
SCANsat.Unity/Unity/SCAN_MainMap.cs | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/SCANmechjeb/SCANmechjeb.cs b/SCANmechjeb/SCANmechjeb.cs
index 5d57023d1..64258f3be 100644
--- a/SCANmechjeb/SCANmechjeb.cs
+++ b/SCANmechjeb/SCANmechjeb.cs
@@ -37,8 +37,8 @@ class SCANmechjeb : MonoBehaviour
private bool shutdown, mjOnboard, mjTechTreeLocked;
// Store references to required fields - type name changes between Target / Hidden and target / hidden across MechJeb versions
- private static FieldInfo targetField;
- private static FieldInfo hiddenField;
+ private readonly static FieldInfo targetField;
+ private readonly static FieldInfo hiddenField;
///
/// Reflectively fetches MechJebCore fields, tolerating the
diff --git a/SCANsat.Unity/Unity/SCAN_MainMap.cs b/SCANsat.Unity/Unity/SCAN_MainMap.cs
index 164d35e68..b362c9be5 100644
--- a/SCANsat.Unity/Unity/SCAN_MainMap.cs
+++ b/SCANsat.Unity/Unity/SCAN_MainMap.cs
@@ -148,11 +148,11 @@ public void setMap(ISCAN_MainMap map)
if (m_TypeLabel != null)
{
// Show initial display mode
- string label = "Terrain";
+ string label = "Altimetry";
switch (map.MapType)
{
case (MainMapDisplayMode.Terrain):
- label = "Terrain";
+ label = "Altimetry";
break;
case (MainMapDisplayMode.Biome):
label = "Biome";
@@ -574,7 +574,7 @@ public void ToggleType(bool isOn)
switch (nextMode)
{
case (MainMapDisplayMode.Terrain):
- label = "Terrain";
+ label = "Altimetry";
break;
case (MainMapDisplayMode.Biome):
label = "Biome";
From ffc9f7918c6c4213ccd7dc7486ecb95a15622572 Mon Sep 17 00:00:00 2001
From: Azrail09 <43310214+Azrail09Code@users.noreply.github.com>
Date: Sat, 1 Aug 2026 07:02:35 +1200
Subject: [PATCH 20/20] Set SCANsat version file to the right location.
---
GameData/SCANsat/SCANsat/SCANsat.version | 7 -------
SCANsat/SCANsat.csproj | 4 ++--
2 files changed, 2 insertions(+), 9 deletions(-)
delete mode 100644 GameData/SCANsat/SCANsat/SCANsat.version
diff --git a/GameData/SCANsat/SCANsat/SCANsat.version b/GameData/SCANsat/SCANsat/SCANsat.version
deleted file mode 100644
index 2b4319d5c..000000000
--- a/GameData/SCANsat/SCANsat/SCANsat.version
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "NAME": "SCANsat",
- "VERSION": "21.1.0.0",
- "KSP_VERSION": "1.12",
- "KSP_VERSION_MIN": "1.12.3",
- "KSP_VERSION_MAX": "1.12"
-}
diff --git a/SCANsat/SCANsat.csproj b/SCANsat/SCANsat.csproj
index 66ff03d2a..b3c8f2d39 100644
--- a/SCANsat/SCANsat.csproj
+++ b/SCANsat/SCANsat.csproj
@@ -10,8 +10,8 @@
true
-
- $(KSPBT_ModRoot)/SCANsat/SCANsat.version
+
+ $(KSPBT_ModRoot)/SCANsat.version
1.12.3