Skip to content

Commit

Permalink
More Kittopia related changes
Browse files Browse the repository at this point in the history
* Removed the ability to delete various parameters, like the ScaledSpace Material
* Moved the Utility / Exporting code from Kittopia to Kopernicus
* Added KittopiaActions to export the planet config / textures
StollD committed May 21, 2018
1 parent f648f4b commit 263b0d3
Showing 12 changed files with 1,193 additions and 40 deletions.
25 changes: 21 additions & 4 deletions src/Kopernicus/Configuration/AtmosphereFromGroundLoader.cs
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@ public NumericParser<Boolean> DEBUG_alwaysUpdateAll
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -75,6 +76,7 @@ public NumericParser<Boolean> doScale
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -108,6 +110,7 @@ public NumericParser<Single> innerRadius
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -127,6 +130,7 @@ public ColorParser invWaveLength
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -160,6 +164,7 @@ public NumericParser<Single> outerRadius
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -176,6 +181,7 @@ public NumericParser<Single> samples
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -201,7 +207,7 @@ public NumericParser<Single> samples
"The scale of the atmosphere mesh in all three directions. Automatically set if doScale is enabled.")]
public Vector3Parser transformScale
{
get { return Value.doScale ? Vector3.zero : Value.transform.localScale; }
get { return Value.transform.localScale; }
set
{
Value.transform.localScale = value;
@@ -210,6 +216,7 @@ public Vector3Parser transformScale
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -229,6 +236,7 @@ public ColorParser waveLength
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -246,6 +254,7 @@ public NumericParser<Single> outerRadiusMult
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -263,6 +272,7 @@ public NumericParser<Single> innerRadiusMult
{
CalculatedMembers(Value);
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);
}
}
}
@@ -274,6 +284,8 @@ public NumericParser<Single> innerRadiusMult
public void Destroy()
{
// Remove the Atmosphere from Ground
Value.planet.afg = null;
AFGInfo.atmospheres.Remove(Value.planet.transform.name);
AtmosphereFromGround[] afgs = Value.transform.parent.GetComponentsInChildren<AtmosphereFromGround>();
foreach (AtmosphereFromGround afg in afgs)
{
@@ -284,7 +296,7 @@ public void Destroy()
MaterialSetDirection[] msds = Value.transform.parent.GetComponentsInChildren<MaterialSetDirection>();
foreach (MaterialSetDirection msd in msds)
{
UnityEngine.Object.Destroy(msd.gameObject);
UnityEngine.Object.Destroy(msd);
}
}

@@ -416,12 +428,17 @@ public AtmosphereFromGroundLoader(CelestialBody body)
renderer.sharedMaterial = new MaterialWrapper.AtmosphereFromGround();
MeshFilter meshFilter = scaledAtmosphere.AddComponent<MeshFilter>();
meshFilter.sharedMesh = Templates.ReferenceGeosphere;
Value = scaledAtmosphere.AddComponent<AtmosphereFromGround>();
Value = body.afg = scaledAtmosphere.AddComponent<AtmosphereFromGround>();
Value.planet = body;
Value.sunLight = Sun.Instance.gameObject;
Value.mainCamera = ScaledCamera.Instance.transform;
AFGInfo.StoreAFG(Value);
AFGInfo.PatchAFG(Value);

// Set defaults
SetDefaultValues();
}

Value.planet = body;
}
}
26 changes: 23 additions & 3 deletions src/Kopernicus/Configuration/Body.cs
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
using Kopernicus.Components;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Kopernicus.OnDemand;
using Kopernicus.UI;
@@ -157,15 +158,17 @@ public NumericParser<Int32> contractWeight

// Celestial body properties (description, mass, etc.)
[ParserTarget("Properties", AllowMerge = true)]
[KittopiaUntouchable]
public PropertiesLoader properties { get; set; }

// Wrapper around KSP's Orbit class for editing/loading
[ParserTarget("Orbit", AllowMerge = true)]
[KittopiaUntouchable]
public OrbitLoader orbit { get; set; }

// Wrapper around the settings for the world's scaled version
[KittopiaUntouchable]
[ParserTarget("ScaledVersion", AllowMerge = true)]
[KittopiaUntouchable]
public ScaledVersionLoader scaledVersion { get; set; }

// Wrapper around the settings for the world's atmosphere
@@ -189,23 +192,40 @@ public NumericParser<Int32> contractWeight
public List<ParticleLoader> particles { get; set; }

// Wrapper around the settings for the SpaceCenter
[KittopiaUntouchable]
[ParserTarget("SpaceCenter", AllowMerge = true)]
[KittopiaUntouchable]
public SpaceCenterLoader spaceCenter { get; set; }

// Wrapper around DebugMode settings
[KittopiaUntouchable]
[ParserTarget("Debug")]
[KittopiaUntouchable]
public DebugLoader debug { get; set; }

// Post spawn orbit patcher
[ParserTarget("PostSpawnOrbit")]
[KittopiaHideOption]
public ConfigNode postSpawnOrbit
{
get { return celestialBody.Get<ConfigNode>("orbitPatches", null); }
set { celestialBody.Set("orbitPatches", value); }
}

[KittopiaAction("Export Body")]
[KittopiaDescription("Exports the body as a Kopernicus config file.")]
public void ExportConfig()
{
ConfigNode config = PlanetConfigExporter.CreateConfig(this);
ConfigNode kopernicus = new ConfigNode("@Kopernicus:NEEDS[!Kopernicus]");
kopernicus.AddNode(config);
ConfigNode wrapper = new ConfigNode();
wrapper.AddNode(kopernicus);

// Save the node
Directory.CreateDirectory(KSPUtil.ApplicationRootPath + "GameData/KittopiaTech/PluginData");
wrapper.Save("GameData/KittopiaTech/PluginData/" + name + ".cfg",
"KittopiaTech - a Kopernicus Visual Editor");
}

// Parser Apply Event
void IParserEventSubscriber.Apply(ConfigNode node)
{
35 changes: 32 additions & 3 deletions src/Kopernicus/Configuration/OceanLoader.cs
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@
using Kopernicus.Configuration.ModLoader;
using Kopernicus.UI;
using UnityEngine;
using UnityEngine.AI;

namespace Kopernicus
{
@@ -111,6 +112,7 @@ public NumericParser<Single> maxQuadLengthsPerFrame

// Surface Material of the PQS
[ParserTarget("Material", AllowMerge = true, GetChild = false)]
[KittopiaUntouchable]
public Material surfaceMaterial
{
get { return Value.surfaceMaterial; }
@@ -119,6 +121,7 @@ public Material surfaceMaterial

// Fallback Material of the PQS (its always the same material)
[ParserTarget("FallbackMaterial", AllowMerge = true)]
[KittopiaUntouchable]
public Material fallbackMaterial
{
get { return Value.fallbackMaterial; }
@@ -127,19 +130,45 @@ public Material fallbackMaterial

// PQSMod loader
[ParserTargetCollection("Mods", AllowMerge = true, NameSignificance = NameSignificance.Type)]
[KittopiaUntouchable]
public List<IModLoader> mods = new List<IModLoader>();

// Killer-Ocean
[ParserTarget("HazardousOcean", AllowMerge = true)]
public FloatCurveParser hazardousOcean
{
get { return Value.gameObject.GetComponent<HazardousOcean>()?.heatCurve; }
set { Value.gameObject.AddOrGetComponent<HazardousOcean>().heatCurve = value; }
get
{
HazardousOcean ocean = Value.gameObject.GetComponent<HazardousOcean>();
if (ocean == null)
{
return null;
}
return ocean.heatCurve;
}
set
{
HazardousOcean ocean = Value.gameObject.GetComponent<HazardousOcean>();
if (value == null && ocean != null)
{
UnityEngine.Object.Destroy(ocean);
return;
}
if (value != null && ocean == null)
{
ocean = Value.gameObject.AddComponent<HazardousOcean>();
}

if (value != null)
{
ocean.heatCurve = value;
}
}
}

// Ocean-Fog
[KittopiaUntouchable]
[ParserTarget("Fog", AllowMerge = true)]
[KittopiaUntouchable]
public FogLoader fog;

/// <summary>
2 changes: 1 addition & 1 deletion src/Kopernicus/Configuration/OrbitLoader.cs
Original file line number Diff line number Diff line change
@@ -149,7 +149,7 @@ public ColorParser color
}
KopernicusOrbitRendererData data =
(KopernicusOrbitRendererData) PSystemManager.OrbitRendererDataCache[Value];
return data.orbitColor;
return data.nodeColor;
}
set
{
9 changes: 9 additions & 0 deletions src/Kopernicus/Configuration/PQSLoader.cs
Original file line number Diff line number Diff line change
@@ -159,6 +159,7 @@ public EnumParser<PQSMaterialType> materialType

// Surface Material of the PQS
[ParserTarget("Material", AllowMerge = true, GetChild = false)]
[KittopiaUntouchable]
public Material surfaceMaterial
{
get { return Value.surfaceMaterial; }
@@ -167,6 +168,7 @@ public Material surfaceMaterial

// Fallback Material of the PQS (its always the same material)
[ParserTarget("FallbackMaterial", AllowMerge = true, GetChild = false)]
[KittopiaUntouchable]
public Material fallbackMaterial
{
get { return Value.fallbackMaterial; }
@@ -175,6 +177,7 @@ public Material fallbackMaterial

// PQSMod loader
[ParserTargetCollection("Mods", AllowMerge = true, NameSignificance = NameSignificance.Type)]
[KittopiaUntouchable]
public List<IModLoader> mods = new List<IModLoader>();

/// <summary>
@@ -489,6 +492,12 @@ public PQSLoader(CelestialBody body)
}
}

[KittopiaDestructor]
public void Destroy()
{
UnityEngine.Object.Destroy(Value.gameObject);
}

// Apply Event
void IParserEventSubscriber.Apply(ConfigNode node)
{
2 changes: 1 addition & 1 deletion src/Kopernicus/Configuration/PropertiesLoader.cs
Original file line number Diff line number Diff line change
@@ -202,8 +202,8 @@ public NumericParser<Double> navballSwitchRadiusMultLow
}

// Science values of this body
[KittopiaUntouchable]
[ParserTarget("ScienceValues", AllowMerge = true)]
[KittopiaUntouchable]
public ScienceValuesLoader scienceValues { get; set; }

// Biome definition via MapSO parser
15 changes: 14 additions & 1 deletion src/Kopernicus/Configuration/ScaledVersionLoader.cs
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
using Kopernicus.MaterialWrapper;
using Kopernicus.OnDemand;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Kopernicus.Components;
@@ -78,8 +79,8 @@ public NumericParser<Single> fadeEnd
}

// Create the Kopernicus LightShifter
[KittopiaUntouchable]
[ParserTarget("Light", AllowMerge = true)]
[KittopiaUntouchable]
public LightShifterLoader lightShifter
{
get
@@ -103,6 +104,7 @@ public LightShifterLoader lightShifter

// Coronas for a star's scaled version
[ParserTargetCollection("Coronas", NameSignificance = NameSignificance.None)]
[KittopiaUntouchable]
public List<CoronaLoader> coronas = new List<CoronaLoader>();

[ParserTarget("sphericalModel")]
@@ -120,6 +122,7 @@ public NumericParser<Boolean> deferMesh
}

[ParserTarget("Material", AllowMerge = true)]
[KittopiaUntouchable]
public Material material
{
get
@@ -153,6 +156,12 @@ public void RebuildScaledSpace()
Utility.UpdateScaledMesh(Value.scaledBody, Value.pqsController, Value, Body.ScaledSpaceCacheDirectory,
Value.Get("cacheFile", ""), Value.Get("exportMesh", true), sphericalModel);
}

[KittopiaAction("Rebuild ScaledSpace Textures")]
public IEnumerator RebuildTextures()
{
return PlanetTextureExporter.UpdateTextures(Value, new PlanetTextureExporter.TextureOptions());
}

// Parser apply event
void IParserEventSubscriber.Apply(ConfigNode node)
@@ -346,6 +355,10 @@ public ScaledVersionLoader(CelestialBody body)
Value = body;
coronas = Value.scaledBody.GetComponentsInChildren<SunCoronas>(true).Select(c => new CoronaLoader(c))
.ToList();
if (!coronas.Any())
{
coronas = null;
}

// Figure out what kind of body we are
if (Value.scaledBody.GetComponentsInChildren<SunShaderController>(true).Length > 0)
187 changes: 160 additions & 27 deletions src/Kopernicus/Configuration/SpaceCenterLoader.cs
Original file line number Diff line number Diff line change
@@ -45,7 +45,14 @@ public class SpaceCenterLoader : BaseLoader, IParserEventSubscriber, ITypeParser
public NumericParser<Double> latitude
{
get { return Value.latitude; }
set { Value.latitude = value; }
set
{
Value.latitude = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// longitude
@@ -54,14 +61,28 @@ public NumericParser<Double> latitude
public NumericParser<Double> longitude
{
get { return Value.longitude; }
set { Value.longitude = value; }
set
{
Value.longitude = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

[ParserTarget("repositionRadial")]
public Vector3Parser repositionRadial
{
get { return Value.repositionRadial; }
set { Value.repositionRadial = value; }
set
{
Value.repositionRadial = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// decalLatitude
@@ -70,7 +91,14 @@ public Vector3Parser repositionRadial
public NumericParser<Double> decalLatitude
{
get { return Value.decalLatitude; }
set { Value.decalLatitude = value; }
set
{
Value.decalLatitude = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// decalLongitude
@@ -79,71 +107,134 @@ public NumericParser<Double> decalLatitude
public NumericParser<Double> decalLongitude
{
get { return Value.decalLongitude; }
set { Value.decalLongitude = value; }
set
{
Value.decalLongitude = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// lodvisibleRangeMultipler
[ParserTarget("lodvisibleRangeMultipler")]
public NumericParser<Double> lodvisibleRangeMultipler
{
get { return Value.lodvisibleRangeMult; }
set { Value.lodvisibleRangeMult = value; }
set
{
Value.lodvisibleRangeMult = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// reorientFinalAngle
[ParserTarget("reorientFinalAngle")]
public NumericParser<Single> reorientFinalAngle
{
get { return Value.reorientFinalAngle; }
set { Value.reorientFinalAngle = value; }
set
{
Value.reorientFinalAngle = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// reorientInitialUp
[ParserTarget("reorientInitialUp")]
public Vector3Parser reorientInitialUp
{
get { return Value.reorientInitialUp; }
set { Value.reorientInitialUp = value; }
set
{
Value.reorientInitialUp = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// reorientToSphere
[ParserTarget("reorientToSphere")]
public NumericParser<Boolean> reorientToSphere
{
get { return Value.reorientToSphere; }
set { Value.reorientToSphere = value; }
set
{
Value.reorientToSphere = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// repositionRadiusOffset
[ParserTarget("repositionRadiusOffset")]
public NumericParser<Double> repositionRadiusOffset
{
get { return Value.repositionRadiusOffset; }
set { Value.repositionRadiusOffset = value; }
set
{
Value.repositionRadiusOffset = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// repositionToSphere
[ParserTarget("repositionToSphere")]
public NumericParser<Boolean> repositionToSphere
{
get { return Value.repositionToSphere; }
set { Value.repositionToSphere = value; }
set
{
Value.repositionToSphere = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// repositionToSphereSurface
[ParserTarget("repositionToSphereSurface")]
public NumericParser<Boolean> repositionToSphereSurface
{
get { return Value.repositionToSphereSurface; }
set { Value.repositionToSphereSurface = value; }
set
{
Value.repositionToSphereSurface = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// repositionToSphereSurfaceAddHeight
[ParserTarget("repositionToSphereSurfaceAddHeight")]
public NumericParser<Boolean> repositionToSphereSurfaceAddHeight
{
get { return Value.repositionToSphereSurfaceAddHeight; }
set { Value.repositionToSphereSurfaceAddHeight = value; }
set
{
Value.repositionToSphereSurfaceAddHeight = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// position
@@ -152,7 +243,14 @@ public NumericParser<Boolean> repositionToSphereSurfaceAddHeight
public Vector3Parser position
{
get { return Value.position; }
set { Value.position = value; }
set
{
Value.position = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// radius
@@ -161,31 +259,59 @@ public Vector3Parser position
public NumericParser<Double> radius
{
get { return Value.radius; }
set { Value.radius = value; }
set
{
Value.radius = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// heightMapDeformity
[ParserTarget("heightMapDeformity")]
public NumericParser<Double> heightMapDeformity
{
get { return Value.heightMapDeformity; }
set { Value.heightMapDeformity = value; }
set
{
Value.heightMapDeformity = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// absoluteOffset
[ParserTarget("absoluteOffset")]
public NumericParser<Double> absoluteOffset
{
get { return Value.absoluteOffset; }
set { Value.absoluteOffset = value; }
set
{
Value.absoluteOffset = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// absolute
[ParserTarget("absolute")]
public NumericParser<Boolean> absolute
{
get { return Value.absolute; }
set { Value.absolute = value; }
set
{
Value.absolute = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// groundColor
@@ -194,7 +320,14 @@ public NumericParser<Boolean> absolute
public ColorParser groundColorParser
{
get { return Value.color; }
set { Value.color = value; }
set
{
Value.color = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// Texture
@@ -203,14 +336,14 @@ public ColorParser groundColorParser
public Texture2DParser groundTextureParser
{
get { return Value.mainTexture; }
set { Value.mainTexture = value; }
}

[KittopiaAction("Update KSC")]
[KittopiaDescription("Updates and applies the parameters of the KSC object")]
public void UpdateKSC()
{
Value.Start();
set
{
Value.mainTexture = value;
if (!Injector.IsInPrefab)
{
Value.Start();
}
}
}

// Apply event
3 changes: 3 additions & 0 deletions src/Kopernicus/Kopernicus.csproj
Original file line number Diff line number Diff line change
@@ -217,6 +217,9 @@
<Compile Include="UI\KittopiaDestructor.cs" />
<Compile Include="UI\KittopiaHideOption.cs" />
<Compile Include="UI\KittopiaUntouchable.cs" />
<Compile Include="UI\PlanetConfigExporter.cs" />
<Compile Include="UI\PlanetTextureExporter.cs" />
<Compile Include="UI\Tools.cs" />
<Compile Include="Utility.cs" />
</ItemGroup>
<ItemGroup>
292 changes: 292 additions & 0 deletions src/Kopernicus/UI/PlanetConfigExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/**
* Kopernicus Planetary System Modifier
* -------------------------------------------------------------
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* This library is intended to be used as a plugin for Kerbal Space Program
* which is copyright 2011-2017 Squad. Your usage of Kerbal Space Program
* itself is governed by the terms of its EULA, not the license above.
*
* https://kerbalspaceprogram.com
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Kopernicus.Configuration;
using KSP.Localization;
using Object = System.Object;

namespace Kopernicus
{
namespace UI
{
/// <summary>
/// Converts a celestial body into a Kopernicus config
/// </summary>
public static class PlanetConfigExporter
{
public static ConfigNode CreateConfig(CelestialBody celestialBody)
{
return CreateConfig(new Body(celestialBody));
}

public static ConfigNode CreateConfig(Body body)
{
// Create the ConfigNode
ConfigNode bodyNode = new ConfigNode("Body");

// Export the planet to the config
WriteToConfig(body, ref bodyNode);

return bodyNode;
}

private static void WriteToConfig(Object value, ref ConfigNode node)
{
// If the value can export to config node directly
if (value is IConfigNodeWritable)
{
ConfigNode values = ((IConfigNodeWritable) value).ValueToNode();
if (values != null)
{
node.AddData(values);
}

return;
}

// Get all ParserTargets from the object
Dictionary<ParserTarget, MemberInfo> parserTargets = Tools.GetParserTargets(value.GetType());

// Export all found targets
foreach (KeyValuePair<ParserTarget, MemberInfo> keyValuePair in parserTargets)
{
ParserTarget parserTarget = keyValuePair.Key;
MemberInfo memberInfo = keyValuePair.Value;

// Is this a collection or a single value?
if (Tools.IsCollection(parserTarget))
{
ProcessCollection(parserTarget, memberInfo, value, ref node);
}
else
{
ProcessSingleValue(parserTarget, memberInfo, value, ref node);
}
}
}

/// <summary>
/// Adds a single value ParserTarget to the ConfigNode tree
/// </summary>
private static void ProcessSingleValue(ParserTarget parserTarget, MemberInfo memberInfo, Object reference,
ref ConfigNode node)
{
// Get the value of the MemberInfo
Object value = Tools.GetValue(memberInfo, reference);
if (value == null)
{
return;
}

// Is this a value or a node?
ConfigType configType = Tools.GetConfigType(value.GetType());
if (configType == ConfigType.Value)
{
SetValue(parserTarget, memberInfo, reference, ref node);
}
else
{
// Create the new node
String name = parserTarget.FieldName;
if (parserTarget.NameSignificance == NameSignificance.Type)
{
name += ":" + value.GetType().Name;
}

ConfigNode valueNode = node.AddNode(name);
WriteToConfig(value, ref valueNode);
}
}

/// <summary>
/// Adds a multi-value ParserTarget to the ConfigNode tree
/// </summary>
private static void ProcessCollection(ParserTarget parserTarget, MemberInfo memberInfo, Object reference,
ref ConfigNode node)
{
// Get the type of the collection
Type memberType = Tools.MemberType(memberInfo);

// Is the collection a dictionary?
if (typeof(IDictionary).IsAssignableFrom(memberType))
{
IDictionary dictionary = Tools.GetValue(memberInfo, reference) as IDictionary;

// Is the dictionary null?
if (dictionary == null)
{
return;
}

// Create the new ConfigNode
ConfigNode targetNode = null;

// Iterate over the elements of the dictionary
foreach (DictionaryEntry value in dictionary)
{
// Null-Check
if (value.Key == null || value.Value == null)
{
continue;
}

// Create the node if neccessary
if (targetNode == null)
{
targetNode = node;
if (parserTarget.FieldName != "self")
{
targetNode = node.AddNode(parserTarget.FieldName);
}
}

// The first generic type has to be ConfigType.Value, figure out the type of the second one
ConfigType type = Tools.GetConfigType(value.Value.GetType());

// If it is a node, add it to the node
if (type == ConfigType.Node)
{
ConfigNode valueNode = targetNode.AddNode(Tools.FormatParsable(value.Key));
WriteToConfig(value.Value, ref valueNode);
}
else
{
targetNode.AddValue(Tools.FormatParsable(value.Key), Tools.FormatParsable(value.Value));
}
}
}
else if (typeof(IList).IsAssignableFrom(memberType))
{
IList list = Tools.GetValue(memberInfo, reference) as IList;

// Is the dictionary null?
if (list == null)
{
return;
}

// Create the new ConfigNode
ConfigNode targetNode = null;

// Iterate over the elements of the list
foreach (Object value in list)
{
// Null-Check
if (value == null)
{
continue;
}

// Create the node if neccessary
if (targetNode == null)
{
targetNode = node;
if (parserTarget.FieldName != "self")
{
targetNode = node.AddNode(parserTarget.FieldName);
}
}

// Figure out the config type of type
ConfigType type = Tools.GetConfigType(value.GetType());

// If it is a node, add it to the node
if (type == ConfigType.Node)
{
String name = "Value";
if (parserTarget.NameSignificance == NameSignificance.Key)
{
name = parserTarget.Key;
}

if (parserTarget.NameSignificance == NameSignificance.Type)
{
name = value.GetType().Name;
}

ConfigNode valueNode = targetNode.AddNode(name);
WriteToConfig(value, ref valueNode);
}
else
{
String name = "value";
if (parserTarget.NameSignificance == NameSignificance.Key)
{
name = parserTarget.Key;
}

if (parserTarget.NameSignificance == NameSignificance.Type)
{
name = value.GetType().Name;
}

targetNode.AddValue(name, Tools.FormatParsable(value));
}
}
}
}

/// <summary>
/// Formats and adds a ParserTarget to a ConfigNode
/// </summary>
private static void SetValue(ParserTarget parserTarget, MemberInfo memberInfo, Object reference,
ref ConfigNode node)
{
// Get the value behind the MemberInfo
Object value = Tools.GetValue(memberInfo, reference);
if (value == null)
{
return;
}

// Format the value
String formattedValue = Tools.FormatParsable(value);
if (formattedValue == null)
{
return;
}

formattedValue = Localizer.Format(formattedValue);

// Get a description
String description = Tools.GetDescription(memberInfo);

// Add it to the config
if (String.IsNullOrEmpty(description))
{
node.AddValue(parserTarget.FieldName, formattedValue);
}
else
{
node.AddValue(parserTarget.FieldName, formattedValue, description);
}
}
}
}
}
256 changes: 256 additions & 0 deletions src/Kopernicus/UI/PlanetTextureExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;

namespace Kopernicus
{
namespace UI
{
/// <summary>
/// Exports the celestial body maps of a body
/// </summary>
public class PlanetTextureExporter
{
[RequireConfigType(ConfigType.Node)]
public class TextureOptions
{
[ParserTarget("exportColor")]
public NumericParser<Boolean> ExportColor;

[ParserTarget("exportHeight")]
public NumericParser<Boolean> ExportHeight;

[ParserTarget("exportNormal")]
public NumericParser<Boolean> ExportNormal;

[ParserTarget("transparentMaps")]
public NumericParser<Boolean> TransparentMaps;

[ParserTarget("saveToDisk")]
public NumericParser<Boolean> SaveToDisk;

[ParserTarget("applyToScaled")]
public NumericParser<Boolean> ApplyToScaled;

[ParserTarget("normalStrength")]
public NumericParser<Single> NormalStrength;

public TextureOptions()
{
ExportColor = true;
ExportHeight = true;
ExportNormal = true;
TransparentMaps = true;
SaveToDisk = true;
ApplyToScaled = true;
NormalStrength = 7;
}
}

public static IEnumerator UpdateTextures(CelestialBody celestialBody, TextureOptions options)
{
// Get time
DateTime now = DateTime.Now;

// If the user wants to export normals, we need height too
if (options.ExportNormal)
{
options.ExportHeight = true;
}

// Prepare the PQS
PQS pqsVersion = celestialBody.pqsController;

// If the PQS is null, abort
if (pqsVersion == null)
{
throw new InvalidOperationException();
}

// Tell the PQS that we are going to build maps
pqsVersion.isBuildingMaps = true;
pqsVersion.isFakeBuild = true;

// Get the mod building methods from the PQS
Action<PQS.VertexBuildData> modOnVertexBuildHeight =
(Action<PQS.VertexBuildData>) Delegate.CreateDelegate(
typeof(Action<PQS.VertexBuildData>),
pqsVersion,
typeof(PQS).GetMethod("Mod_OnVertexBuildHeight",
BindingFlags.Instance | BindingFlags.NonPublic));
Action<PQS.VertexBuildData> modOnVertexBuild = (Action<PQS.VertexBuildData>) Delegate.CreateDelegate(
typeof(Action<PQS.VertexBuildData>),
pqsVersion,
typeof(PQS).GetMethod("Mod_OnVertexBuild", BindingFlags.Instance | BindingFlags.NonPublic));

// Disable the PQS
pqsVersion.gameObject.SetActive(false);

// Get all mods the PQS is connected to
PQSMod[] mods = pqsVersion.GetComponentsInChildren<PQSMod>()
.Where(m => m.sphere == pqsVersion && m.modEnabled).ToArray();

// Create the Textures
Texture2D colorMap = new Texture2D(pqsVersion.mapFilesize, pqsVersion.mapFilesize / 2,
TextureFormat.ARGB32,
true);
Texture2D heightMap = new Texture2D(pqsVersion.mapFilesize, pqsVersion.mapFilesize / 2,
TextureFormat.RGB24,
true);

// Arrays
Color[] colorMapValues = new Color[pqsVersion.mapFilesize * (pqsVersion.mapFilesize / 2)];
Color[] heightMapValues = new Color[pqsVersion.mapFilesize * (pqsVersion.mapFilesize / 2)];


// Create a VertexBuildData
PQS.VertexBuildData data = new PQS.VertexBuildData();

// Display
ScreenMessage message = ScreenMessages.PostScreenMessage("Generating Planet-Maps", Single.MaxValue, ScreenMessageStyle.UPPER_CENTER);
yield return null;

// Loop through the pixels
for (Int32 y = 0; y < pqsVersion.mapFilesize / 2; y++)
{
for (Int32 x = 0; x < pqsVersion.mapFilesize; x++)
{
// Update Message
Double percent = (Double) (y * pqsVersion.mapFilesize + x) /
(pqsVersion.mapFilesize / 2 * pqsVersion.mapFilesize) * 100;
while (CanvasUpdateRegistry.IsRebuildingLayout()) Thread.Sleep(10);
message.textInstance.text.text = "Generating Planet-Maps: " + percent.ToString("0.00") + "%";

// Update the VertexBuildData
data.directionFromCenter =
QuaternionD.AngleAxis(360d / pqsVersion.mapFilesize * x, Vector3d.up) *
QuaternionD.AngleAxis(90d - 180d / (pqsVersion.mapFilesize / 2f) * y, Vector3d.right)
* Vector3d.forward;
data.vertHeight = pqsVersion.radius;

// Build from the Mods
Double height = Double.MinValue;
if (options.ExportHeight)
{
modOnVertexBuildHeight(data);

// Adjust the height
height = (data.vertHeight - pqsVersion.radius) * (1d / pqsVersion.mapMaxHeight);
if (height < 0)
{
height = 0;
}
else if (height > 1)
{
height = 1;
}

// Set the Pixels
heightMapValues[y * pqsVersion.mapFilesize + x] =
new Color((Single) height, (Single) height, (Single) height);
}

if (options.ExportColor)
{
modOnVertexBuild(data);

// Adjust the Color
Color color = data.vertColor;
if (!pqsVersion.mapOcean)
{
color.a = 1f;
}
else if (height > pqsVersion.mapOceanHeight)
{
color.a = options.TransparentMaps ? 0f : 1f;
}
else
{
color = pqsVersion.mapOceanColor.A(1f);
}

// Set the Pixels
colorMapValues[y * pqsVersion.mapFilesize + x] = color;
}
}

yield return null;
}

// Serialize the maps to disk
String path = KSPUtil.ApplicationRootPath + "/GameData/KittopiaTech/PluginData/" +
celestialBody.transform.name + "/";
Directory.CreateDirectory(path);

// Colormap
if (options.ExportColor)
{
// Save it
colorMap.SetPixels(colorMapValues);
yield return null;

if (options.SaveToDisk)
{
File.WriteAllBytes(path + celestialBody.transform.name + "_Color.png", colorMap.EncodeToPNG());
yield return null;
}

// Apply it
if (options.ApplyToScaled)
{
colorMap.Apply();
celestialBody.scaledBody.GetComponent<MeshRenderer>().material.SetTexture("_MainTex", colorMap);
}
}

if (options.ExportHeight)
{
heightMap.SetPixels(heightMapValues);
yield return null;

if (options.SaveToDisk)
{
File.WriteAllBytes(path + celestialBody.transform.name + "_Height.png",
heightMap.EncodeToPNG());
yield return null;
}

if (options.ExportNormal)
{
// Bump to Normal Map
Texture2D normalMap = Utility.BumpToNormalMap(heightMap, options.NormalStrength);
yield return null;

if (options.SaveToDisk)
{
File.WriteAllBytes(path + celestialBody.transform.name + "_Normal.png",
normalMap.EncodeToPNG());
yield return null;
}

// Apply it
if (options.ApplyToScaled)
{
normalMap.Apply();
celestialBody.scaledBody.GetComponent<MeshRenderer>().material
.SetTexture("_BumpMap", normalMap);
}
}
}

// Close the Renderer
pqsVersion.isBuildingMaps = false;
pqsVersion.isFakeBuild = false;

// Declare that we're done
ScreenMessages.RemoveMessage(message);
ScreenMessages.PostScreenMessage("Operation completed in: " + (DateTime.Now - now).TotalMilliseconds + " ms", 2f, ScreenMessageStyle.UPPER_CENTER);
}
}
}
}
381 changes: 381 additions & 0 deletions src/Kopernicus/UI/Tools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Kopernicus;
using Kopernicus.UI;
using UnityEngine;
using Object = System.Object;

namespace Kopernicus
{
namespace UI
{
public static class Tools
{
/// <summary>
/// Returns all ParserTargets in a specific type
/// </summary>
public static Dictionary<ParserTarget, MemberInfo> GetParserTargets(Type parserType)
{
// Create the dictionary
Dictionary<ParserTarget, MemberInfo> targets = new Dictionary<ParserTarget, MemberInfo>();

// Get all fields and properties from the type
MemberInfo[] members = parserType.GetMembers()
.Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property).ToArray();

// Iterate over the members to check if they are ParserTargets
foreach (MemberInfo memberInfo in members)
{
// Get all ParserTargets from the current member
ParserTarget[] parserTargets = GetAttributes<ParserTarget>(memberInfo);

if (parserTargets == null || !parserTargets.Any())
{
continue;
}

// And add them to the dictionary
targets.Add(parserTargets[parserTargets.Length - 1], memberInfo);
}

return targets;
}

/// <summary>
/// Returns all ParserTargets in a specific type
/// </summary>
public static Dictionary<ParserTarget, MemberInfo> GetParserTargets<T>()
{
return GetParserTargets(typeof(T));
}

/// <summary>
/// Returns whether the ParserTarget describes a single value or a range of values
/// </summary>
public static Boolean IsCollection(ParserTarget parserTarget)
{
return parserTarget is ParserTargetCollection;
}

/// <summary>
/// Returns the real type of a MemberInfo
/// </summary>
public static Type MemberType(MemberInfo member)
{
if (member.MemberType == MemberTypes.Field)
{
return ((FieldInfo) member).FieldType;
}

if (member.MemberType == MemberTypes.Property)
{
return ((PropertyInfo) member).PropertyType;
}

return null;
}

/// <summary>
/// If a ParserTarget has a description, return it
/// </summary>
public static String GetDescription(MemberInfo memberInfo)
{
KittopiaDescription[] descriptions = GetAttributes<KittopiaDescription>(memberInfo);
if (descriptions == null || !descriptions.Any())
{
return null;
}

return descriptions[0].description;
}

/// <summary>
/// Gets the config type that is required by the member
/// </summary>
public static ConfigType GetConfigType(Type memberType)
{
// Add exceptions for String and ConfigNode
if (memberType == typeof(String))
{
return ConfigType.Value;
}

if (memberType == typeof(ConfigNode))
{
return ConfigType.Node;
}

// Get the RequireConfigType Attribute
RequireConfigType[] configTypes =
memberType.GetCustomAttributes(typeof(RequireConfigType), false) as RequireConfigType[];

if (configTypes == null || !configTypes.Any())
{
Debug.Log(memberType);
throw new InvalidOperationException("Member needs to have a parsable type.");
}

return configTypes[0].Type;
}

/// <summary>
/// Returns the value that is assigned to a MemberInfo
/// </summary>
public static Object GetValue(MemberInfo member, Object reference)
{
if (member.MemberType == MemberTypes.Field)
{
return ((FieldInfo) member).GetValue(reference);
}

if (member.MemberType == MemberTypes.Property)
{
PropertyInfo info = (PropertyInfo) member;
if (info.CanRead)
{
return info.GetValue(reference, null);
}
}

return null;
}

/// <summary>
/// Sets the value that is assigned to a MemberInfo
/// </summary>
public static void SetValue(MemberInfo member, Object reference, Object value)
{
if (member.MemberType == MemberTypes.Field)
{
((FieldInfo) member).SetValue(reference, value);
}

if (member.MemberType == MemberTypes.Property)
{
PropertyInfo info = (PropertyInfo) member;
if (info.CanWrite)
{
info.SetValue(reference, value, null);
}
}
}

/// <summary>
/// Return whether the member has an ignore marker attached
/// </summary>
public static Boolean HasAttribute<T>(MemberInfo memberInfo) where T : Attribute
{
Object[] values =
memberInfo.GetCustomAttributes(typeof(T), false);
return values.Any();
}

/// <summary>
/// Return whether the member has an ignore marker attached
/// </summary>
public static T[] GetAttributes<T>(MemberInfo memberInfo) where T : Attribute
{
return memberInfo.GetCustomAttributes(typeof(T), false) as T[];
}

/// <summary>
/// Converts a value to a parsable string representation
/// </summary>
public static String FormatParsable(Object value)
{
if (value == null)
{
return null;
}

if (value is String)
{
return (String) value;
}

if (value is IParsable)
{
return ((IParsable) value).ValueToString();
}

return value.ToString();
}

/// <summary>
/// Sets the value of a parsertarget using a string
/// </summary>
public static void SetValueFromString(MemberInfo member, Object reference, String value)
{
// Get the current value
Object current = GetValue(member, reference);
Object backup = current;

try
{
// Get the type of the member
Type memberType = MemberType(member);

// Is the member a string member?
if (memberType == typeof(String))
{
// Simply assign the new value
SetValue(member, reference, value);
return;
}

// Is the member a parsable type?
if (typeof(IParsable).IsAssignableFrom(memberType))
{
// Is the member null?
if (current == null)
{
SetValue(member, reference, current = Activator.CreateInstance(memberType));
}

// Now we can parse the value
IParsable parser = (IParsable) current;
parser.SetFromString(value);

// Reapply
SetValue(member, reference, parser);
return;
}
}
catch (Exception e)
{
Debug.LogException(e);
SetValue(member, reference, backup);
return;
}

// The member wasn't parsable
throw new InvalidOperationException("The member wasn't parsable");
}

/// <summary>
/// Parses and applies the user input to a ParserTarget
/// </summary>
public static String ApplyInput(MemberInfo member, String input, Object reference)
{
SetValueFromString(member, reference, input);
return FormatParsable(GetValue(member, reference)) ?? "";
}

/// <summary>
/// Calls the methods declared as KittopiaDestructor
/// </summary>
public static void Destruct(Object value)
{
// Get all methods of the object
MethodInfo[] methods = value.GetType()
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

for (Int32 i = 0; i < methods.Length; i++)
{
// Is the method a KittopiaDestructor?
if (HasAttribute<KittopiaDestructor>(methods[i]))
{
methods[i].Invoke(value, null);
}
}
}

/// <summary>
/// Calls the methods declared as KittopiaDestructor
/// </summary>
public static Object Construct(Type type, CelestialBody body)
{
// Get all methods of the object
ConstructorInfo[] methods =
type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

for (Int32 i = 0; i < methods.Length; i++)
{
// Is the method a KittopiaDestructor?
if (HasAttribute<KittopiaConstructor>(methods[i]))
{
KittopiaConstructor attr = GetAttributes<KittopiaConstructor>(methods[i])[0];
if (attr.parameter == KittopiaConstructor.Parameter.Empty)
{
return methods[i].Invoke(null);
}

if (attr.parameter == KittopiaConstructor.Parameter.CelestialBody)
{
return methods[i].Invoke(new Object[] {body});
}
}
}

return null;
}

/// <summary>
/// Returns all KittopiaActions in a specific type
/// </summary>
public static Dictionary<KittopiaAction, MethodInfo> GetKittopiaActions(Type parserType)
{
// Create the dictionary
Dictionary<KittopiaAction, MethodInfo> targets = new Dictionary<KittopiaAction, MethodInfo>();

// Get all methods from the type
MethodInfo[] members =
parserType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

// Iterate over the members to check if they are KittopiaActions
foreach (MethodInfo memberInfo in members)
{
// Get all KittopiaActions from the current member
KittopiaAction[] actions = GetAttributes<KittopiaAction>(memberInfo);

if (actions == null || !actions.Any())
{
continue;
}

// And add them to the dictionary
targets.Add(actions[0], memberInfo);
}

return targets;
}

/// <summary>
/// Calls a KittopiaAction function
/// </summary>
public static void InvokeKittopiaAction(MethodInfo method, Object reference, Action callback)
{
// Is the method an enumerator?
if (typeof(IEnumerator).IsAssignableFrom(method.ReturnType))
{
IEnumerator coroutine = (IEnumerator) method.Invoke(reference, null);
HighLogic.fetch.StartCoroutine(CoroutineCallback(coroutine, callback));
}
else
{
// Simply invoke the method and call the callback
method.Invoke(reference, null);
callback();
}
}

/// <summary>
/// Executes a coroutine and executes code after it finished
/// </summary>
private static IEnumerator CoroutineCallback(IEnumerator coroutine, Action callback)
{
while(coroutine.MoveNext())
{
yield return coroutine.Current;
}

yield return null;

callback();
}
}
}
}

0 comments on commit 263b0d3

Please sign in to comment.